C++ IO库:IO类型间的关系、流的条件状态、管理输出缓冲

IO 类

​ 为了支持不同种类的 IO 处理操作,在 istream 和 ostream 之外,还定义了其他一些 IO 类型。分别定义了三个独立的头文件:iostream 定义了用于读写流的基本类型,fstream 定义了读写命名文件的类型,sstream 定义了读写内存 string 对象的类型。

头文件 iostream:

类型: istream,wistream (从流读取数据);ostream,wostream (向流写入数据);iostream,wiostream (读写流)

头文件 fstream:

类型: ifstream,wiftream (向文件读取数据);ofstream,wofstream (向文件写入数据);fstream,wfstream (读写文件)

头文件 sstream:

类型: istringstream,wistringstream (从 string 读取数据);ostringstream,wostringstream (向 string 写入数据);stringstream,wstringstream (读写 string)

其中: 宽字符版本的类型和函数的名字以一个 w 开始。例如:wcin、wcout 和 wcerr 分别对应 cin、cout、cerr 的宽字符版本。宽字符版本的类型和对象与其对应的普通 char 版本的类型定义在同一个头文件中。

IO类型间的关系

​ 概念上,设备类型和字符大小都不会影响我们要执行的 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 库的条件状态:(注:strm 表示一种 IO 类型)

strm::iostate			// iostate 是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit			// 用来指出流已崩溃
strm::failbit			// 用来指出一个 IO 操作失败了
strm::eofbit			// 用来指出流已经到达文件结束
strm::goodbit			// 用来指出流未处于错误状态。此值保证为 0
s.eof()					// 若流 s 的 eofbit 置位,返回 true
s.fail()				// 若流 s 的 failbit 或 badbit 置位,返回 true
s.bad()					// 与上同理
s.good()				// 与上同理
s.clear()				// 将流 s 中所有条件状态复位,将流的状态设置为有效。返回 void
s.clear(flags)			// 根据给定的 flags 标志位,将流 s 中对应条件状态位复位。flags 类型为
    					// strm::iostate。返回 void
s.setstate(flags);		// 根据给定的 flags 标志位,将流 s 中对应条件状态位置位。flags 类型为
    					// strm::iostate。返回 void
s.rdstate();			// 返回流 s 的当前条件状态,返回类型为 strm::iostate

​ 下面是一个 IO 错误的例子:

	int val;
	cin >> val;

键入 Boo,读操作就会失败。类似的,如果输入一个文件结束标识,cin 也会进入错误状态。

​ 一个流一旦发生错误,其后续的 IO 操作都会失败。所以代码通常应该在使用一个流之前检查它是否处于良好状态:

while(cin >> word) {
    // ok:读操作成功 ...
}
查询流的状态

​ 将流作为条件使用,只能知道流是否有效。

​ IO 库定义了一个与机器无关的 iostate 类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合来使用 (即一个数的二进制每个位来表示对应的标志位)。通常与位运算一起使用来一次性检测或设置多个标志位。

​ badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位,流就无法再使用。在发生可恢复错误后,failbit 被置位。这种问题通常是可以修正的,流还可以继续使用。如果达到文件结束位置,eofbit 和 failbit 都会被置位。goodbit 的值为 0,表示流未发生错误。如果 badbit、failbit 和 eofbit 任一个被置位,则检测流状态的条件会失败。

​ 标准库还定义了一组函数来查询这些标志位的状态。操作 good 在所有错误位均未置位的情况下返回 true,而 bad、fail 和 eof 则在对应错误位被置位时返回 true。此外,在 badbit 被置位时,fail 也会返回 true。这意味着,使用 good 或 fail 是确定流的总体状态的正确方法。实际上,我们将流当做条件使用的代码就等价于 !fail()。而 eof 和 bad 操作只能表示特定的错误。

管理条件状态

​ 流对象的 rdstate 成员返回一个 iostate 值,对应流的当前状态。setstate 操作将给定条件位置位,表示发生了对应错误。

​ clear 不接受参数的版本复位所有错误标志位。执行 clear() 后,调用 good 会返回 true。

我们可以这样使用这些成员:

auto old_state = cin.rdstate();		// 记住 cin 的当前状态
cin.clear();						// 使 cin 有效
process_input(cin);					// 使用 cin
cin.setstate(old_state);			// 将 cin 置位原有状态

​ 带参数的 clear 接受一个 iostate 值,表示流的新状态。为了复位单一的条件状态位,我们首先用 rdstate 读出当前条件状态,然后用位操作将所需位复位来生成新的状态。

​ 例如,下面代码将 failbit 和 badbit 复位,但保持 eofbit 不变:

cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
管理输出缓冲

​ 每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如:

os << "please enter a value:";

文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。

​ 导致缓冲刷新 (即,数据真正写到输出设备或文件) 的原因有很多:

  • 程序正常结束,作为 main 函数的 return 操作的一部分,缓冲刷新被执行。
  • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区
  • 可以使用操纵符如 endl 来显示刷新缓冲区
  • 在每个输出操作之后,可以用操纵符 unitbuf 设置流的内部状态,来清空缓冲区。默认情况下,对 cerr 是设置 unitbuf 的,因此写到 cerr 的内容都是立即刷新的
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin 和 cerr 都关联到 cout。因此,读 cin 或写 cerr 都会导致 cout 的缓冲区被刷新。
刷新输出缓冲区

​ 操纵符 endl,它完成换行并刷新缓冲区的工作。IO 库中还有两个类似的操纵符:flush 和 ends。flush 刷新缓冲区,但不输出任何额外的字符;ends 向缓冲区插入一个空字符,然后刷新缓冲区:

cout << "hi!" << endl;		// 输出 hi! 和一个换行,然后刷新缓冲区
cout << "hi!" << flush;		// 输出 hi!,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" << ends; 		// 输出 hi! 和一个空字符,然后刷新缓冲区
unitbuf 操纵符

​ 如果想在每次输出后都刷新缓冲区,我们可以使用 unitbuf 操纵符。它告诉流在接下来的每次写操作之后都进行一次 flush 操作。而 nounitbuf 操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。

cout << unitbuf;		// 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf;		// 回到正常的缓冲方式

警告:如果程序崩溃,输出缓冲区不会被刷新

关联输入和输出流

​ 当一个输入流被关联到输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将 cin 和 cout 关联在一起,因此下面语句

cin >> val;

导致 cout 的缓冲区刷新。

交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来

​ tie 有两个重载的版本:不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie 的第二个版本接受一个指向 ostream 的指针,将自己关联到此 ostream。即,x.tie(&o) 将流 x 关联到输出流 o。

​ 我们既可以将一个 istream 对象关联到另一个 ostream,也可以将一个 ostream 关联到另一个 ostream:

cin.tie(&cout);				// 仅仅用来展示:将 cin 和 cout 关联在一起
// old_tie 指向当前关联到 cin 的流 (如果有的话)
ostream *old_tie = cin.tie(nullptr);		// cin 不再与其他流关联
cin.tie(&cerr);				// 将 cin 与 cerr 关联;cin 应该关联到 cout,这不是一个好主意
cin.tie(old_tie);			// 重建 cin 与 cout 的关联

在这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了 tie。为了彻底解开流的关联,我们传递了一个空指针。每个流同时最多关联一个流,但多个流可以同时关联到同一个 ostream。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值