【Cpp程序设计】输入输出流
本章的主要内容是流类,标准流对象,使用流操纵算子控制输出格式,调用
cout的成员函数,以及cin的高级用法。在了解C++程序输入输出的规则的同时,我们也要进一步理解继承多态的概念。
流类
cin和cout输入和输出数据,另外,我们的程序还应该可以在文件中读入数据,以及向文件中写入数据。数据输入和输出的过程也是数据传输的过程,数据像水流一样从一个地方流动到另一个地方,因此,在C++中将此过程称为“流”(stream),而在C++的标准模板库中,将用于进行数据输入输出的类统称为“流类”。例如cin就是istream类的对象,cout是ostream类的对象。图1展示出了流类中的派生关系。

C++是可以多继承的,所以在流类中,ios是抽象的基类,更为具体的说,ios_base是一个抽象类作为最基本的基类,从中派生出模板类basic_ios类,用来管理流缓冲区(streambuf)和异常状态(如failbit、eofbit)。输入类istream和输出类ostream都是从basic_ios中派生而来的,然后istream和ostream共同派生出iostream类。在从ios派生出istream和ostream类时均使用了虚继承,避免多继承的二义性。
istream:用于输入的流类,cin就是该类的对象。ostream:用于输出的流类,cout就是该类的对象。ifstream:用于文件读取数据的类。ofstream:用于文件写入数据的类。iostream:是既能用于输入,又能用于输出的类。fstream:即可以从文件中读取数据,又能像文件写入数据的类。
标准流对象
iostream头文件中定义了四个标准流对象,分别为:cin、cout、cerr和clog。
cin:对应于标准输入流,用于从键盘读取数据,也可以重定向为从文件中读取数据。cout:对应于标准输出流,用于向键盘输出数据,也可以被重定向为像文件写入数据。cerr:对应于标准错误输出流,用于像屏幕输出出错信息,不能被重定向。clog:对应于标准错误输出流,用于像屏幕输出出错信息,不能被重定向。
cerr和clog不同之处在于:cerr不适用缓冲区,直接向显示器输出信息;而输出到clog中的信息会先被存放到缓冲区,缓冲区满或者刷新时才输出到屏幕。
cout是ostream类的对象。ostream类的无参构造函数和复制构造函数都是私有的,因此一般情况下,程序中无法定义ostream对象,唯一可以进行使用的是ostream类的对象cout。当然,ostream是由共有的构造函数的,但是一般情况下没有必要去定义一个新的对象。
cout可以被重定向。所谓重定向,就是将输入的源或输出的目的地改变。例如,cout本应该输出到屏幕上,但是经过重定向,本该输出到屏幕的东西就可以被输出到文件中,例如如下的程序:
1 |
|
freopen是一个标准库函数,参数w代表写的模式,第三的参数代表标准输出。这个语句的作用就是将标准输出重定向到了test.txt文件中,在重定向之后,所有对cout的输出都不再出现再屏幕上了,而是会出现在重定向的位置,重定向仅对本程序有效,不会影响其它程序。在上述程序中,输入6 2可以看到屏幕上没有输出,而在test.txt中有一个字符3。而如果输入4 0,则在屏幕中输出error.。这说明cerr并不会被重定向。
另外,cin也是可以被重定向的,如果在程序中加入freopen("input.dat","r",stdin);,第二个参数r代表读入方式,第三个参数stdin代表标准输入。执行此语句后,cin就不再从键盘读入数据,而是从input.dat文件中读入数据。
使用流操纵算子控制输出格式
cout进行输出时,可以通过操纵算子进行格式控制。C++中常用的输出流操纵算子(也称格式控制符)如表1所示,在使用它们时,我们需要引用头文件iomanip。
| 流操纵算子 | 作用 | |
| *dec | 以十进制形式输出整数 | 常用 |
| hex | 以十六进制形式输出整数 | |
| oct | 以八进制形式输出整数 | |
| fixed | 以普通小数形式输出浮点数 | |
| scientific | 以科学计数法形式输出浮点数 | |
| left | 左对齐,即在宽度不足时将填充字符添加到右边 | |
| *right | 右对齐,即在宽度不足时将填充字符添加到左边 | |
| setbase(b) | 设置输出整数时的进制,b=8、10或16 | |
| setw(w) | 指定输出宽度为w个字符,或输入字符串时读入w个字符 | |
| setfill(c) | 在指定输出宽度的情况下,输出的宽度不足时用字符c填充(默认情况使用空格填充) | |
| setprecision | 设置输出浮点数的精度为n。在使用非fixed且非scientific方式输出的情况下,n即为有效数字最多的位数,如果有效数字位数超过n,则小数部分四舍五入,或自动变为科学计数法输出并保留一共n位有效数字留在使用fixed方式和scientific方式输出的情况下,n时小数点后面应保留的位数 | |
| setiosflags(标志) | 将某个输出格式标志置为1 | |
| resetiosflags(标志) | 将某个输出格式标志置为0 | |
| boolapha | 把true和false输出为字符串 | 不常用 |
| *noboolalpha | 把true和false输出为0、1 | |
| showbase | 输出表示数值的进制的前缀 | |
| *noshowbase | 不输出表示数值的进制的前缀 | |
| showpoint | 总是输出小数点 | |
| *noshowpoint | 只有当小数部分存在时才显示小数点 | |
| showpos | 在非负数值中显示+ | |
| *noshowpos | 在非负数值中不显示+ | |
| *skipws | 输入时跳过空白字符 | |
| noskipws | 输入时不跳过空白字符 | |
| uppercase | 十六进制数中使用'A'~'E'。若输出前缀,则前缀输出'0X',科学计数法中输出'E' | |
| *nouppercase | 十六进制数中使用'a'~'e'。若输出前缀,则前缀输出'0x',科学计数法中输出'e' | |
| internal | 数值的符号(正负号)在指定宽度内左对齐,数值右对齐,中间由字符填充 | |
注意,“流操纵算子”栏中的*不是算子的一部分,星号表示在没有使用任何算子的情况下,就等效于使用了该算子,即默认情况。使用这些算子的方法就是将算子用<<和cout连用,例如:cout<<hex<<12<<","<<24;这条语句的意思就是以16进制的形式输出后面的两个数,因此输出结果应该为c,18。
setiosflags算子实际上是一个库函数,它以一些标志作为参数,这些标志可以是在iostream头文件中定义的以下几种取值,他们的含义与同名算子一样:
| ios::left | ios::right | ios::internal |
| ios::showbase | ios::showpoint | ios::unppercase |
| ios::showpos | ios::scientific | ios::fixed |
这些标志实际上都是仅有某比特位为1,而其他比特位都为0的整数。多个标志可以使用|运算符连接,表示同时设置,例如:cout<<setiosflags(ios::scientific|ios::showpos)<<12.34,输出结果应该为+1.234000e+001。如果两个相互矛盾的标志同时被设置,如先设置setiosflags(ios::fixed),然后有设置setiosflags(ios::scientific),那么结果可能导致两个标志都不起作用,所以在设置某个对立的标志时,这时应该使用resetiosflag清除原先的标志,例如:
1 | cout<<setiosflags(ios::fixed)<<12.34<<endl; |
这样的输出结果应该为:
1 | 12.340000 |
另外需要注意的是,setw算子所起的作用是一次性的,即只影响下一次的输出。每次需要指定输出宽度时都要使用setw。我们看一个比较全面的示例程序:
1 |
|
输出结果为:
1 | (1)8d 141 215 |
另外,需要说明的是,setw算子还可以影响cin的的行为,例如:
1 |
|
当我们输入1234567890时,输出结果为1234,567,这说明setw(4)使得读入s1时,只读入4个字符。setw作用于cin时,同样只影响下一次的输入。
调用cout的成员函数
ostream类有一些成员函数,通过cout可以调用它们,也能用于控制输出格式,起作用和流操纵算子相同,如表2所示:
| 成员函数 | 作用相同的流操纵算子 |
| precision(n) | setprecision(n) |
| width(w) | setw(w) |
| fill(c) | setfill(c) |
| setf(标志) | setiosflags(标志) |
| unsetf(标志) | resetiosflags(标志) |
setf和unsetf函数用到的“标志”和setiosflags,resetiosflags用到的完全相同,这些成员函数的用法十分简单,例如:
1 | cout.setf(ios::scientific); |
输出的结果为1.22300000e+001。cout有一个成员函数put,可以用来输出一个字符。其参数类型为int,代表要输出字符的ASCII码,其返回值是cout的引用,例如:
1 | cout.put('a'); |
输出结果是abcz,输入的'a'会自动转化为ASCII码值。
cin的高级用法
判断输入结束
cin可以用来从键盘输入数据。在输入数据的多少不确定时,且没有结束标志的情况下,该如何判断写入数据已经读完了呢?例如:输入若干个正整数,输出其中的最大值,程序该如何编写呢?
1 |
|
在windows系统中,通过键盘输入时,在单独的一行按ctrl+z键后再按回车键,就代表输入结束。因此程序运行时,输入若干个正整数后换行,再按ctrl+z键和回车键,程序就会输出最大值并结束。即表达式cin>>n在碰到ctrl+z时就会返回false。在UNIX/Linux系统中,ctrl+d代表结束。如果将标准输入重定向为某个文件,如在程序开始添加freopen("text.txt","r",stdin);语句,或者不添加上述语句,在windows的“命令提示符”窗口中输入:mycin<text.txt(注意上述文件编译为mycin.exe),这样也可以重定向输入位置。在这种情况下,test.txt文件中并不需要包含ctrl+z,只要有用空格或回车隔开的若干个正整数即可。cin读到文件末尾时,cin>>n就会返回false,从而导致程序结束。
在前面,“运算符重载”中提到istream类中将>>算符重载为成员函数,而且这些函数的返回值是cin的引用。准确的是,cin>>n的返回值是istream&类型的,而while语句中的条件表达式的返回值是bool类型、整数类型或其他和整数类型兼容的类型,istream&显然和整数类型不兼容,为什么while(cin>>n)还能成立呢?这是因为istream类对强制类型转换运算符bool进行重载,这使得cin对象可以被自动转换成bool类型。所谓自动类型转换,就是调用cin的operator bool()函数,而该成员函数可以返回某个标志值,该标志值在cin没有读到结尾时为true,读到结尾时就会变为false。
istream成员函数
istream类作为输入流类,有一些很有用的成员函数。
get
istream类中有很多名为get的成员函数,这里介绍一个原型为int get();的函数。它的作用是从输入流中读入一个字符,返回其ASCII码值。如果碰到输入末尾,则返回值为EOF。EOF是指End of Flie ,在istream类中从输入流中读取数据的成员函数,在把输入数据都读取完后再进行读取,就会返回EOF,EOF是在iostream类中定义的一个整形常量,值为-1。
注意,get函数不会跳过空格、制表符、回车等特殊字符,所有的字符都能被读入,例如:
1 |
|
当我们输入Hello World!并回车时,会输出Hello world!并回车,只有输入^z,即ctrl+z时可以停止这个程序。
getline
getline成员函数有两个版本:
1 | istream& getline(char* buf,int bufSize); |
第一个版本是从输入流中读取bufSize-1个字符到缓冲区buf,或者读到\n为止,函数自动会在buf中读入数据的结尾添加\0。第二个版本与第一个版本的区别是,不再是读到\n为止,而是读到delim字符为止。\n或delim都不会读入buf中,但是会被输入流取走。这两个函数的返回值就是函数所作用的对象的引用。如果输入流中\n或delim之前的字符个数达到或超过dufSize,就会导致读入出错,其结果是:虽然本次读入已经完成,但是之后的读入都会失败。
从输入流中读入一行,可以使用第一个版本,用cin>>s(假设s是string对象或char*指针)则不行,因为这种写法在读到空格、制表符时就会停止,因此就不能保证s中读入的是整行。这里给出一个示例:
1 |
|
我们谈一谈如果第6行输入的字符超过5个,例如输入ab cd123456k,则输出为:
1 | ab cd123456k |
第11行中没有读入,因为cin内部存在错误标记,不能继续输入,而后面清楚标记后,输入还是从输入流中输入,因此123456被读入n中。
eof
bool eof();此函数可以用来判断输入流是否已经结束。返回true表示已经结束了。
ignore
istream& ignore(int n=1,int delim=EOF);此函数的作用为跳过输入流中的n个字符,或跳过delim及其之前的所有字符,哪个条件先满足就按哪个条件执行。两个参数都是默认值,因此cin.ignore()就等效于cin.ignore(1,EOF),即跳过一个字符。该函数常用于删掉输入的无效部分,例如Tel:63652823,Tel:就是无效内容。给出一个程序示例:
1 |
|
当我们输入abcde34时,输出为34。当我们输入abA34时,输出为34。分别对应上面的两种情况。
peek
int peek();此函数返回输入流的下一个字符,但是并不将该字符从输入流中取走,只是看一眼下一个字符是什么。cin.peek()不会跳过输入流中的空格、回车符。在输入已经结束的情况下,cin.peek()会返回EOF。
putback
istream& putback(char c)作用为将一个字符插入到输入流的最前面。

