快速网站备案东莞网络优化公司
IO 库
C++ 不直接处理输入输出,而是通过定义一族定义在标准库当中的类型来处理IO。
8.1 IO 类
为了支持不同种类的 IO 处理操作,除了 istream 和 ostream 之外,标准库还定义了其它 IO 类型。这些类型分别定义在三个独立的头文件当中:
- iostream 定义了用于读写流的基本类型;
- fstream 定义了读写命名文件的类型;
- sstream 定义了读写内存 string 对象的类型;
IO 类型间的关系
设备类型和字符大小不会影响我们要执行的 IO 操作。例如,我们可以使用 >>
读取数据,不用管数据的来源是来自于控制台窗口,还是来自于磁盘文件或 string 读取。
标准库是我们可以忽略这些不同类型的流之间的差异,这是通过**继承机制(inheritance)**来实现的。
利用模板,可以使用具有继承关系的类,而不必了解继承机制如何工作的细节。
8.1.1 IO 对象无拷贝或赋值
我们不能拷贝或对 IO 对象赋值:
ofstream out1, out2;
out1 = out2; // 错误❌: 不能对流对象赋值
ofstream print(ofstream); // 错误❌: 不能初始化 ofstream 参数
out2 = print(out2); // 错误❌: 不能拷贝流对象
由于不能拷贝 IO 对象,因此不能将形参或返回类型设置为流类型。进行 IO 操作的函数通常以引用方式传递和返回流(其中,以引用的方式传递和返回流已经在之前的章节当中出现过多次,比如实现一个 read 函数作为 Sales_item 类的接口,从标准输入流读取数据,来对 Sales_item 类进行初始化。read 函数的实现部分正是将输入流作为引用进行参数传递和返回)。
读写一个 IO 对象会改变其状态,因此传递和返回的引用不能是 const 的。
8.1.2 条件状态
IO 操作的一个根本问题是,它是有可能发生错误的。某些错误是可恢复的,而有一些错误可能发生在系统深处,超出了应用程序自身的修正范围。
以下是一个 IO 错误的例子:
int ival;
cin >> ival;
如果我们从标准输入键入Boo
,读操作就会失败,因为输入运算符期待的是int
类型的输入,以和ival
匹配。一个流一旦发生错误,其上后续的 IO 操作都会失败。
查询流的状态
有时我们需要直到流为什么会失败,而不仅仅需要知道流是否有效。
IO 库定义了一个与机器无关的 iostate 类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合来使用。IO 库定义了 4 个 iostate 类型的 constexpr 值,表示特定的位模式。这些值用来表示特定类型的 IO 条件,可以与位运算符一起使用来一次性检测或设置多个标志位。
badbit 表示系统级错误,如不可恢复的读写错误。通常,一旦 badbit 被置位,则流就无法使用了。
在发生可恢复错误后,failbit 被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可修复的,流还可以继续使用。
如果到达文件结束位置,eofbit 和 failbit 都会被置为。
goodbit 的值为 0,表示流未发生错误。如果 badbit、failbit 和 eofbit 任意一个被置位,则检测流状态的条件会失败。
标准库还定义了一组函数来查询这些标志位的状态。操作 good 在所有错误均为被置位的情况下返回 true,而 bad、fail 和 eof 则在对应错误位置被置位时返回 true。
管理条件状态
8.1.3 管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。
刷新输出缓冲区
操作符 endl 可以完成换行并刷新缓冲区。
IO 库中还有另外两个相似的操作符,分别是 flush 和 ends。
flush 刷新缓冲区,但不输出任何额外的字符。
ends 向缓冲区插入一个空字符,然后刷新缓冲区。
unitbuf 操作符
如果想要在每一个输出操作后都刷新缓冲区,可以使用 unitbuf 操作符。它将会告诉输出流,接下来的每次写操作之后都进行一次 flush 操作。而 nounitbuf 会重置流:
cout << unitbuf; // 后续的所有输出操作会立即刷新缓冲区
cout << nounitbuf; // 重置
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库直接将 cout 和 cin 关联在一起,因此cin >> ival;
这条语句将导致 cout 的缓冲区被刷新。
交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。
tie 有两个重载的版本:一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针;如果对象未关联到流,则返回空指针。
tie 的第二个版本接受一个指向 ostream 的指针,将自己关联到此 ostream。即:x.tie(&o)
将x
关联到输出流o
。
我们既可以将 istream 对象关联到另一个 ostream,也可以将一个 ostream 关联到另一个 ostream:
cin.tie(&cout);
ostream *old_tie = cin.tie(nullptr); // cin 不再与其它流关联
cin.tie(&cerr); // 读取 cin 会刷新 cerr, 而非 cout
cin.tie(old_tie); // 重建 cin 和 cout 的正常关联
每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream。