标准库中定义的 IO
处理分别在三个独立的头文件中:
iostream
定义了用于读写流的基本类型。fstream
定义了读写命名文件的类型。sstream
定义了读写内存string
对象的类型。
为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵 wchar_t
类型的数据。宽字符版本的类型和函数的名字是以 w
开始。例如:wcin
,wcout
,wcerr
分别对应 cin
,cout
,cerr
的宽字符版本。
宽字符版本的类型和对象与其普通 char
版本的类型定义在同一个头文件中,例如,头文件 fstream
定义了 ifstream
和 wifstream
类型。
设备类型和字符大小都不会影响我们要执行的 IO
操作。例如,可以使用 >> 读取数据,而不管是从一个控制台窗口,一个磁盘文件,还是一个 string
读取。类似的,也不用管读取的字符是存入一个 char
对象还是需要一个wchar_t
对象来存储。
IO 对象无拷贝和赋值
不能拷贝或对IO对象赋值:
ofstream out1, out2;
out1 = out2; //错误,不能对流对象赋值
ofstream print(ofstream); //错误,不能初始化 ofstream 参数
out2 = print(out2); //错误,不能拷贝流对象
- 由于不能拷贝
IO
对象,因此不能将形参或函数返回值设置为流类型。 - 进行
IO
操作的函数通常以引用的方式传递和返回流。 - 读写一个
IO
对象会改变其状态,因此传递和返回的引用不能是const
。
条件状态
IO
操作与生俱来的一个问题就是可能发生错误,一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序的可修整范围。
IO
类定义了一些函数和标志,可以帮助访问和操作流的条件状态。
操作 | 含义 |
---|---|
stream::iostate | iostate 是一种机器无关的类型,提供了表达条件状态的完整功能 |
stream::badbit | iostate类型的值,用来指出流已崩溃 |
stream::failbit | iostate类型的值,用来指出一个IO操作失败 |
stream::eofbit | iostate类型的值,用来指出流到达了文件结束 |
stream::goodbit | iostate类型的值,用来指出流未处于错误状态,此值保证为0 |
s.eof() | 若流s的eofbit置位,则返回true |
s.fail() | 若流s的failbit、badbit置位,则返回true |
s.bad() | 若流s的badbit置位,则返回true |
s.good() | 若流s处于有效状态,则返回true |
s.clear() | 若流s中所有条件状态位复位,将流的状态设置为有效,返回void |
s.clear(flag) | 根据给定的flag标志位,将流s中对应的条件状态位复位,flag的类型是iostate,返回void |
s.setstate(flag) | 根据给定的flag标志位,将流s中对应的条件状态位置位,flag的类型为iostate,返回void |
s.rdstate() | 返回流s的当前条件状态,返回值类型为iostate |
int ival;
cin >> ival;
上面的代码期待输入一个整数,如果输入一个字符或者文件结束符标志,cin
就会进入错误状态。
一个流一旦发生错误,其上的后续 IO 都会失败,只有当一个流处于无错误状态时,才可以从它读取数据,向它写入数据。
确定一个流对象的状态的最简单方法是将它当作一个条件使用:
while(cin >> word)
//ok...
如果输入成功,流保持有效状态,条件为真。
查询流的状态
IO
库定义了一个与机器无关的类型 iostate
,它提供了表达流状态的完整功能,这个类型作为一个位集合来使用,可以与运算符一起使用来一次性检测或设置多个标志位。
babit
表示系统级错误,如不可恢复的读写错误。通常,一旦badbit
被设置,流就无法在使用了。failbit
表示发生可恢复错误,如期望读取数值但是却读取了字符,这种问题是可以修正的,流还可以继续使用。- 如果到达了文件莫我们,
eofbit
和failbit
都会被置位。 goodbit
的值为0,表示流没有发生错误。
如果 badbit
、failbit
、和 eofbit
中任意一个被设置,则检测流状态的条件都是失败。
标准库还定义了一组函数用来查询这些状态标志。
good
在所有错误均未置位的情况下返回true
。bad
、fail
、eof
在对应的错误发生时返回true
,此外,当badbit
被置位时,fail
也会返回true
。- 使用
good
、fail
是确定流的总体状态的正确方法。
管理条件状态
rdstate
返回一个iostate
值,对应流的当前状态。setstate
操作将给定条件置位,表示发生了对应的错误。clear
是一个重载函数:- 不接受任何参数的版本清除所有的错误标志位,执行
clear()
后,调用good
返回true
。 - 带参数的
clear
接受一个iostate
值,表示流的新状态,为了复位单一的条件状态位,一般先用rdstate
读取当前的条件状态,然后用位操作将所需位复位来生成新的状态。
- 不接受任何参数的版本清除所有的错误标志位,执行
auto old_state = cin.rdstate(); //记住当前cin的状态
cin.clear(); //使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //恢复cin的原有状态
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);//复位failbit,badbit保持其它标志不变
管理输出缓冲区
每个输出流都管理一个缓冲区,用来保存程序读写的数据,例如:
os<<"please enter a value:"
文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区,随后再打印。
有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许将操作系统多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
缓冲刷新,即数据真正写到输出设备或文件的原因:
- 程序正常结束,作为
main
函数的return
操作的一部分,缓冲刷新被执行。 - 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入到缓冲区。
- 可以使用操纵符
endl
来显示刷新缓冲区。 - 每个输出操作之后,可以用操纵符
unitbuf
设置流的内部状态,来清空缓冲区。默认情况下,对cerr
是设置unitbuf
的,因此写到cerr
的内容都是立即刷新。 - 一个输出流可能被关联到另一流,在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,
cin
和cerr
都关联到cout
,因此,读cin
或写cerr
都会导致cout
的缓冲区被刷新。
注意:
如果程序异常终止,输出缓冲区是不会被刷新的,当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
刷新输出缓冲区
endl
能够完成换行和刷新缓冲区的工作。flush
刷新缓冲区,但不输出任何额外的字符。ends
向缓冲区插入一个空字符,然后刷新缓冲区。
cout << "hi" << endl; //输出hi和一个换行符,然后刷新缓冲区
cout << "hi" << flush; //输出hi,然后刷新缓冲区,不附加任何往外的字符
cout << "hi" << ends; //输出hi和一个空字符,然后刷新缓冲区
unitbuf 操纵符
如果想在每次输出操作后都刷新缓冲区,可以使用 unitbuf
操纵符,它告诉流在接下来每次写操作之后都进行一次 flush
操作,nounitbuf
操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
cout<<unitbuf; //所有输出操作后都会立即刷新到缓冲区
// 任何输出都立即刷新,无缓冲
cout<<nounitbuf; //回到正常的刷新模式
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流,标准库将cout
和 cin
关联在一起,因此, cin >> ival;
,将会导致 cout
缓冲区被刷新。
交互式系统通常应该关联输入流和输出流,这意味着所有输出,包括用户提示信息都会在读操作之前被打印。
将两个流关联到一起使用 tie
,tie
有两个版本:
- 一个版本不带参数,返回指向输出流的指针,如果对象当前关联到一个输出流,则返回的是指向这个流的指针,如果对象未关联流,则返回空指针。
- 第二个版本接受一个指向
ostream
的指针,将自己关联到此ostream
。
可以将一个istream对象关联到另一个ostream对象,也可以将一个ostream对象关联到另一个ostream对象。
cin.tie(&cout); //cin和cout相关联
ostream *old_tie = cin.tie(nullptr); //cin不再与其他流关联
cin.tie(&cerr); //cin 关联到cerr,读取cin会刷新cerr
cin.tie(old_tie); //重建cin和cout之间的正常关联
每个流同时最多关联到一个流,但多个流可以同时关联到同一个流。