Bootstrap

【C++篇】C++的输入和输出

友情链接:C/C++系列系统学习目录

知识总结顺序参考C Primer Plus(第六版)和谭浩强老师的C程序设计(第五版)等,内容以书中为标准,同时参考其它各类书籍以及优质文章,以至减少知识点上的错误,同时方便本人的基础复习,也希望能帮助到大家
 
最好的好人,都是犯过错误的过来人;一个人往往因为有一点小小的缺点,将来会变得更好。如有错漏之处,敬请指正,有更好的方法,也希望不吝提出。最好的生活方式就是和努力的大家,一起奔跑在路上



🚀一、C++的输入和输出

⛳(一)C++的标准输入输出

在C语言中,我们通常会使用 scanf、printf 和其他所有标准C输入和输出函数,来对数据进行输入输出操作。在C++语言中,C语言的这一套输入输出库我们仍然能使用,但是 C++ 又增加了一套新的、更容易使用的输入输出库。即iostream库

iostream 库包含两个基础类型 istream 和 ostream,分别表示输入流和输出流。一个流就是一个字符序列,是从IO设备读出或写入IO设备的。术语“流”(stream)想要表达的是,随着时间的推移,字符是顺序生成或消耗的。

标准输入输出对象:

标准库定义了4个IO对象。为了处理输入,我们使用一个名为cin(发音为see-in)的istream类型的对象。这个对象也被称为标准输入( standard input)。对于输出,我们使用一个名为 cout(发音为 see-out)的ostream类型的对象。此对象也被称为标准输出( standard output)。标准库还定义了其他两个ostream对象,名为cerr和clog(发音分别为see-err和 see-log)。我们通常用cerr来输出警告和错误消息,因此它也被称为标准错误(standard error)。而clog用来输出程序运行时的一般性信息。

🎈1.使用cout进行C++输出

(1)基本用法:

cout << "come up and C++ me some time. " ;
  • <<符号表示该语句把这个字符串发送给cout(只是发送该字符串的地址);该符号指出了信息流动的路径

  • 可以用<<符号拼接输出:

    cout << "Now you have " << carrots << " carrots." << endl ;
    
  • cout是一个预定义的对象,对象是类的特点实例

  • 从概念上看,输出是一个流,即从程序流出的一系列字符。cout对象表示这种流,其属性是在iostream文件中定义的。cout的对象属性包括一个插入运算符(<<),它可以将其右侧的信息插入到流中。

  • 与printf复杂的使用转换说明不同,C++插入运算符 (<<)会根据其后的数据类型应地调整其行为,这是一个运算符重载的例子。cout的智能行为源自C++的面向对象特性

C++提供了两种发送消息的方式:一种方式是使用类方法(本质上就是函数调用);另一种方式是重新定义运算符,cin和cout采用的就是这种方式。因此,下面的语句使用重新定义的<<运算符将“显示消息”发送给cout:

//使用运算符重载
cout << "I am not a crook."  
//使用类方法
cout.put('!');
cout.put(ch);

如果熟悉C后才开始学习C++,则可能注意到了,插入运算符(<<)看上去就像按位左移运算符(<<),这是一个运算符重载的例子,通过重载,同一个运算符将有不同的含义。编译器通过上下文来确定运算符的含义。C本身也有一些运算符重载的情况。例如,&符号既表示地址运算符,又表示按位AND运算符;*既表示乘法,又表示对指针解除引用。这里重要的不是这些运算符的具体功能,而是同一个符号可以有多种含义,而编译器可以根据上下文来确定其含义。C++扩展了运算符重载的概念,允许为用户定义的类型(类)重新定义运算符的含义。

(1)格式化输出

ostream类包含一些可用于控制格式的成员函数,能够精确地控制输出的格式—字段宽度、小数位数、采用小数格式还是E格式等,比如setf()

通常cout会删除结尾的零,例如,将3333333.250000显示为3333333.25。调用cout.setf( )将覆盖这种行为

cout.setf(ios_base::fixed, ios_base::floatfield); // fixed-point
cout << 3333.250000;

cout.setf(ios::boolalpha)函数调用设置了一个标记,该标记命令cout显示true和false,而不是1和0。老式C++实现可能要求使用ios:boolalpha,而不是ios_base::boolalpha来作为cout.setf( )的参数。

cout.setf(ios_base: :boolalpha); //a newer C++ feature

下面的语句导致cout在使用定点表示法时,显示三位小数:

std::cout.precision(3) ;

操作符:

**①控制符endl:**表示换行,和cout一样也是在头文件iostream中定义的,且位于名称空间std中

打印字符串时,cout 不会自动移到下一行,第一条cout语句会将光标留在输出字符串的后面。每条cout 语句的输出从前一个输出的末尾开始

cout << "come up and C++ me some time. " << endl;

②进制控制符:

在默认情况下,cout以十进制格式显示整数,而不管这些整数在程序中是如何书写的

int chest = 42;     //decimal integer literal
int waist = 0x42;   // hexadecimal integer literal
int inseam = 042;   //octal integer literal

cout << "chest = " << chest << "(42 in decimal ) \n" ;
cout <<" waist = " << waist e< "(0x42 in hex) \n " ;
cout c< "inseam = " c< inseam << "(042 in octal)\n" ;

//输出:
chest = 42(42 in decimal)
waist = 66(0x42 in hex)
inseam = 34(042 in octal)

控制符dec、hex和oct,分别用于指示cout以十进制、十六进制和八进制格式显示整数,默认格式为十进制,在修改格式之前,原来的格式将一直有效:

cout<<hex;  //不会在屏幕上显示任何内容,而只是修改cout显示整数的方式。

标识符hex位于名称空间std中,而程序使用了该名称空间,因此不能将hex用作变量名。然而,如果省略编译指令using,而使用std::cout、std::endl、std::hex和std::oct,则可以将hex用作变量名。

③换行符\n:

C++还提供了另一种在输出中指示换行的旧式方法:C语言的转义字符:\n,同样表示换行

cout << "what's next?\n";

endl确保程序继续运行前刷新输出(将其立即显示在屏幕上);而使用“\n”不能提供这样的保证,这意味着在有些系统中,有时可能在您输入信息后才会出现提示。

🎈2.使用cin进行C++输入

int carrots;
cin >> carrots;
  • 信息从cin流向carrots,就像C++将输出看作是流出程序的字符流一样,它也将输入看作是流入程序的字符流。iostream文件将cin定义为一个表示这种流的对象。输出时,<<运算符将字符串插入到输出流中;输入时,cin 使用>>运算符从输入流中抽取字符。通常,需要在运算符右侧提供一个变量,以接收抽取的信息(符号<<和>>被选择用来指示信息流的方向)。
  • 与cout一样,cin也是一个智能对象。它可以将通过键盘输入的一系列字符(即输入)转换为接收信息的变量能够接受的类型。
  • 如果cin位于测试条件中,则将被转换为bool类型。如果输入成功,则转换后的值为true,否则为false。

cin与scanf()比较:

从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号。转换说明指示了读取哪种类型的字符

  • cin和scanf()一样使用空白(空格、制表符和换行符)来确定字符串的结束位置,同样会把非数字字符放回输入,这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符

  • scanf使用转换说明,而cin的插入运算符 (>>)会根据其后的数据类型应地调整其行为,如果我们输入的数据类型不匹配,会结束输入,并且不匹配的输入会放回输入缓冲区中,会影响下一次的输入(会自动读取缓冲区内容)需要使用fflush(stdin)清空缓冲区内容

    C++输入类型不匹配:

    会保留 value 不修改并设置 failbit为1 (C++11 前)。写入零到 value 并设置 failbit 。若输入数据对于 value 过大或过小,则写入 std::numeric_limits::max() 或 std::numeric_limits::min() 并设置 failbit 标志。(C++11 起),同时,对cin方法的调用将返回false(如果被转换为bool类型)。

    如果需要继续输入,除了需要使用fflush(stdin)清空缓冲区内容外,还需要使用cin.clear( )方法清除EOF标记,否则无法调用cin

(1)cin处理单字符输入

cin对象支持3种不同模式的单字符输入,其用户接口各不相同。

文件结尾等和C语言一样(实际上属于操作系统的范畴),当检测到EOF后或者输入错误时,cin将两位错误标记(eofbit和failbit)都设置为1。

  • 可以通过成员函数eof( )来查看eofbit是否被设置;如果检测到EOF,则cin.eof( )将返回bool值true,否则返回false。
  • 同样,如果eofbit或failbit被设置为1,则fail( )成员函数返回true,否则返回false。
  • 注意,eof( )和fail( )方法报告最近读取的结果;也就是说,它们在事后报告,而不是预先报告。因此应将cin.eof( )或cin.fail( )测试放在读取后

设置这个标记后,cin将不读取输入,再次调用cin也不管用。对于文件输入,这是有道理的,因为程序不应读取超出文件尾的内容。然而,对于键盘输入,有可能使用模拟EOF来结束循环,但稍后要读取其他输入。cin.clear( )方法可能清除EOF标记,使输入继续进行不过要记住的是,在有些系统中,按Ctrl+Z实际上将结束输入和输出,而cin.clear( )将无法恢复输入和输出。

由于EOF表示的不是有效字符编码,因此可能不与char类型兼容

①使用原始的cin输入

这种情况单字符输入的话,cin忽略空格和换行符

②使用cin.get(ch)

成员函数cin.get(ch)读取输入中的下一个字符(即使它是空格),并将其赋给变量ch

头文件iostream将cin.get(ch)的参数声明为引用类型,因此该函数可以修改其参数的值

每次读取一个字符,直到遇到EOF的输入循环的基本设计如下:

cin.get (ch) ;   // attempt to read a char
while (cin.fail() == false) // test for EOF
{
	...// do stuff
	cin.get(ch) ;  //attempt to read another char
}

//可改写为:
while (!cin.fail())
  • 方法cin.get(char)的返回值是一个cin对象。然而,istream类提供了一个可以将istream对象(如cin)转换为bool值的函数;当cin出现在需要bool值的地方(如在while循环的测试条件中)时,该转换函数将被调用。另外,如果最后一次读取成功了,则转换得到的bool值为true;否则为false。这意味着可以将上述while测试改写为这样:

    while (cin)
    

    这比!cin.fail( )或!cin.eof( )更通用,因为它可以检测到其他失败原因,如磁盘故障。

  • 最后,由于cin.get(char)的返回值为cin,因此可以将循环精简成这种格式:

    while (cin.get(ch))
    

③C语言getchar()和putchar()的替代

cin.get(arr,20)、cin.get()、cin.get(ch)一共三个版本,函数重载允许对多个相关的函数使用相同的名称

“怀旧”的C语言用户可能喜欢C语言中的字符I/O函数—getchar( )和putchar( ),它们仍然适用,只要像在C语言中那样包含头文件stdio.h(或新的cstdio)即可。也可以使用istream和ostream类中类似功能的成员函数,cin.get()和cout.put()

不接受任何参数的cin.get( )成员函数返回输入中的下一个字符。该函数的工作方式与C语言中的getchar( )相似,将字符编码作为int值返回;而cin.get(ch)返回一个对象,而不是读取的字符。同样,可以使用cout.put( )函数来显示字符:

//可以使用int ch,并用cin.get( )代替cin.get(char),用cout.put( )代替cout,用EOF测试代替cin.fail( )测试:
int ch;
ch = cin.get();   // attempt to read a char  
while (ch != EOF) // test for EOF
{
    cout.put(ch); //cout.put (char (ch)) for some implementations
    ++count ;
    ch = cin.get();	
}
  • cout的put(ch)成员函数的工作方式类似C语言中的putchar( ),只不过其参数类型为char,而不是int:最初,put( )成员只有一个原型—put(char)。可以传递一个int参数给它,该参数将被强制转换为char。C++标准还要求只有一个原型。然而,有些C++实现都提供了3个原型:put(char)、put(signed char)和put(unsigned char)。在这些实现中,给put( )传递一个int参数将导致错误消息,因为转换int的方式不止一种。使用显式强制类型转换的原型(如cin.put(char(ch)))可使用int参数。

  • 当该函数到达EOF时,将没有可返回的字符。相反,cin.get( )将返回一个用符号常量EOF表示的特殊值。该常量是在头文件iostream中定义的。EOF值必须不同于任何有效的字符值,以便程序不会将EOF与常规字符混淆。通常,EOF被定义为值−1,因为没有ASCII码为−1的字符,但并不需要知道实际的值,而只需在程序中使用EOF即可。

  • 使用cin.get(ch)(有一个参数)进行输入时,将不会导致任何类型方面的问题。前面讲过,cin.get(char)函数在到达EOF时,不会将一个特殊值赋给ch。事实上,在这种情况下,它不会将任何值赋给ch。

cin.get(char)成员函数调用通过返回转换为false的bool值来指出已到达EOF,而cin.get( )成员函数调用则通过返回EOF值来指出已到达EOF,EOF是在文件iostream中定义的。

(2)面向行的输入:getline()和get()

由于cin使用空白(空格、制表符和换行符)来确定字符串的结束位置,所以每次只会读取一个单词,get和getline所属iostream类,作用是读取一整行,通过换行符确定读取结束,他们都可以读取空格。

区别:getline会在读取结束后舍弃换行符(读取换行符但是不存进数组中,直接丢弃),而get会将换行符保留到输入序列中。

char arr[100];
cout<<"输入一段文字:"<<endl;
cin.getline(arr,20);       //使用getline
cin.get(arr,20);         //使用get

//输入:asdfg 回车
  • get与getline有两个参数,第一个参数是用来存储的数组名称,第二个参数代表着读取的字节数。
  • get:只读取asdfg不读取回车,会导致下一个读取输入时第一个读取“回车”。
  • getline:getline读取asdfg回车,并将回车转换为“\0”读取,所以最终读取的是“asdfg\0”输入序列中无回车,下一个读取将会正常读取。

1.如何解决get不舍弃换行符而产生的影响:

在cin.get(arr,20)使用完后插入“cin.get()”,cin.get()不接受任何参数,为get()的一个变体,意义为读取下一个字符(即使是换行符),用此代码来处理被舍弃的换行符。或者可以将两段代码合为一段:

cin.get(arr,20).get();

2.将一行输入读取到string对象:

使用getline(cin, inputLine):

其中 cin 是正在读取的输入流,而 inputLine 是接收输入字符串的 string 变量的名称。需要注意的是,它不是类方法。

string name;
getline(cin, name);
cout << "Hello, " << name << endl;

3.getline()和get()读取空行:

当get( )(不是getline( ))读取空行后将设置失效位(failbit)。这意味着接下来的输入将被阻断,但可以用下面的命令来恢复输入:

cin.clear();

4.输入行包含的字符数比指定的多:

getline()和get()将把余下的字符留在输入队列中,而getline( )还会设置失效位,并关闭后面的输入。

⛳(二)文件输入/输出

在这里插入图片描述

文件流: 对文件进行读写操作

头文件: <fstream>

类库:

  • ifstream 对文件输入(读文件)
  • ofstream 对文件输出(写文件)
  • fstream 对文件输入或输出

在这里插入图片描述

🎈1.文件输出

文件输出:让程序输出至文件而不是屏幕、写入到文本文件中

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main()
{
    string name;
    int age;
    ofstream outfile; //也可以使用fstream, 但是fstream的默认打开方式不截断文件长度
    
    // ofstream的默认打开方式是, 截断式写入 ios::out | ios::trunc
    // fstream的默认打开方式是, 截断式写入 ios::out
    // 建议指定打开方式
    outfile.open("user.txt", ios::out | ios::trunc);
    
    while (1) {
        cout << "请输入姓名: [ctrl+z退出] ";
        cin >> name;
        if (cin.eof()) { //判断文件是否结束
        	break;
        }
        outfile << name << "\t";

        cout << "请输入年龄: ";
        cin >> age;
        outfile << age << endl; //文本文件写入
    }
    
    // 关闭打开的文件
    outfile.close();
    
    system("pause");
    return 0;
}
  • 必须包含头文件fstream。
  • 头文件fstream定义了一个用于处理输出的ofstream类。需要声明一个或多个ofstream变量(对象),并以自己喜欢的方式对其进行命名,条件是遵守常用的命名规则。
  • 必须指明名称空间std;例如,为引用元素ofstream,必须使用编译指令using或前缀std::。
  • 需要将ofstream对象与文件关联起来。为此,方法之一是使用open( )方法。
  • 使用完文件后,应使用方法close( )将其关闭。
  • 可结合使用ofstream对象和运算符<<来输出各种类型的数据。

🎈2.文件输入

对文件输入:让程序使用文件而不是键盘来输入、读取文本文件

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main()
{
    string name;
    int age;
    ifstream infile;
    infile.open("user.txt");
    
    while (1) {
        infile >> name;
        if (infile.eof()) { //判断文件是否结束
        	break;
    	}
        
        cout << name << "\t";

        infile >> age;
        cout << age << endl;
    }
    
    // 关闭打开的文件
    infile.close();
    
    system("pause");
    return 0;
}
  • 必须包含头文件fstream。
  • 头文件fstream定义了一个用于处理输入的ifstream类。需要声明一个或多个ifstream变量(对象),并以自己喜欢的方式对其进行命名,条件是遵守常用的命名规则。
  • 必须指明名称空间std;例如,为引用元素ifstream,必须使用编译指令using或前缀std::。
  • 需要将ifstream对象与文件关联起来。为此,方法之一是使用open( )方法。
  • 使用完文件后,应使用close( )方法将其关闭。
  • 可结合使用ifstream对象和运算符>>来读取各种类型的数据。
  • 可以使用ifstream对象和get( )方法来读取一个字符,使用ifstream对象和getline( )来读取一行字符。
  • 可以结合使用ifstream和eof( )、fail( )等方法来判断输入是否成功。
  • ifstream对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否则被转换为false。

如果试图打开一个不存在的文件用于输入,情况将如何呢?这种错误将导致后面使用ifstream对象进行输入时失败。检查文件是否被成功打开的首先方法是使用方法is_open( ),为此,可以使用类似于下面的代码:

inFile.open ( "bowling.txt ");
if( !inFile.is_open ())
{
	exit(EXIT_FAILURE);
}

如果文件被成功地打开,方法is_open( )将返回true;因此如果文件没有被打开,表达式!inFile.isopen( )将为true。函数exit( )的原型是在头文件cstdlib中定义的,在该头文件中,还定义了一个用于同操作系统通信的参数值EXIT_FAILURE。函数exit( )终止程序。

🎈3.二进制文件的读写

写二进制文件

使用文件流对象的 write 方法写入二进制数据

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main()
{
    string name;
    int age;
    ofstream outfile;
    outfile.open("user.dat", ios::out | ios::trunc | ios::binary);
    
    while (1) {
        cout << "请输入姓名: [ctrl+z退出] ";
        cin >> name;
        if (cin.eof()) { //判断文件是否结束
        break;
    	}
        outfile << name << "\t";
        
        cout << "请输入年龄: ";
        cin >> age;
        //outfile << age << endl; //会自动转成文本方式写入
        outfile.write((char*)&age, sizeof(age));
    }
    
    // 关闭打开的文件
    outfile.close();
    
    system("pause");
    return 0;
}

读二进制文件

使用文件流对象的 read 方法

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main()
{
    string name;
    int age;
    ifstream infile;
    infile.open("user.dat", ios::in | ios::binary);
    while (1) {
        infile >> name;
        if (infile.eof()) { //判断文件是否结束
            break;
   		}
    	cout << name << "\t";
        
        // 跳过中间的制表符
        char tmp;
        infile.read(&tmp, sizeof(tmp));
        
        //infile >> age; //从文本文件中读取整数, 使用这个方式
        infile.read((char*)&age, sizeof(age));
        cout << age << endl; //文本文件写入
    }
    
    // 关闭打开的文件
    infile.close();
    
    system("pause");
    return 0;
}

🎈4.按指定格式读写文件

#include <fstream>
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

//按指定格式读文件(使用 stringstream)
int main()
{
    string name;
    int age;
    ofstream outfile;
    
    outfile.open("user.txt", ios::out | ios::trunc);
    
    while (1) {
        cout << "请输入姓名: [ctrl+z退出] ";
        cin >> name;
        if (cin.eof()) { //判断文件是否结束
        break;
    }
    
    cout << "请输入年龄: ";
    cin >> age;
    
    stringstream s;
    s << "name:" << name << "\t\tage:" << age << endl;
    	outfile << s.str();
    }
    
    // 关闭打开的文件
    outfile.close();
    
    system("pause");
    return 0;
}

//按指定格式写文件(没有优雅的 C++解决方案, 使用 C 语言的 sscanf)
int main(void)
{
    char name[32];
    int age;
    string line;
    ifstream infile;
    infile.open("user.txt");
    
    while (1) {
        getline(infile, line);
        if (infile.eof()) { //判断文件是否结束
        break;
    }
        
        sscanf_s(line.c_str(), "姓名:%s 年龄:%d", name, sizeof(name),&age);
        cout << "姓名:" << name << "\t\t年龄:" << age << endl;
    }
    
    infile.close();
    
    system("pause");
    return 0;
}

🎈5.文件流的状态检查与定位

  • s.is_open( ):文件流是否打开成功,
  • s.eof( ) :流 s 是否结束
  • s.fail( ):流 s 的 failbit 或者 badbit 被置位时, 返回 true
  • failbit: 出现非致命错误,可挽回, 一般是软件错误,badbit 置位, 出现致命错误, 一般是硬件错误或系统底层错误, 不可挽回
  • s.bad( )”流 s 的 badbit 置位时, 返回 true
  • s.good( ):流 s 处于有效状态时, 返回 true
  • s.clear( ):流 s 的所有状态都被复位

seekg

seekg( off_type offset, ios::seekdir origin ); 
//            偏移量             起始位置

作用:设置输入流的位置

参数 1: 偏移量,参数 2: 相对位置

  • beg 相对于开始位置
  • cur 相对于当前位置
  • end 相对于结束位置
//读取当前程序的最后 50 个字符

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main(void) {
    ifstream infile;
    infile.open("定位.cpp");
    if (!infile.is_open()) {
    	return 1;
    }
    
    infile.seekg(-50, infile.end);
    while (!infile.eof()) {
        string line;
        getline(infile, line);
        cout << line << endl;
    }
    
    infile.close();
    
    system("pause");
    return 0;
}

tellg

返回该输入流的当前位置(距离文件的起始位置的偏移量)

//获取当前文件的长度
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main(void) {
    ifstream infile;
    
    infile.open("定位.cpp");
    if (!infile.is_open()) {
    	return 1;
	}
	
    // 先把文件指针移动到文件尾
    infile.seekg(0, infile.end);
    
    int len = infile.tellg();
    cout << "len:" << len;
    
    infile.close();
    system("pause");
	return 0;
}

seekp

设置该输出流的位置

//先向新文件写入:“123456789”,然后再在第 4 个字符位置写入“ABC”
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main(void) {
    ofstream outfile;
    
    outfile.open("test.txt");
    if (!outfile.is_open()) {
    	return 1;
	}
	
    outfile << "123456789";
    
    outfile.seekp(4, outfile.beg);
    outfile << "ABC";
    
    outfile.close();
    system("pause");
    return 0;
}

行文至此,落笔为终。文末搁笔,思绪驳杂。只道谢不道别。早晚复相逢,且祝诸君平安喜乐,万事顺意。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;