【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)
作用为将一个字符插入到输入流的最前面。