C++学习之IO库

iostream定义了用于读写流的基本类型
fstream定义了读写命名文件的类型
sstream定义了读写内存string对象的类型

1
2
3
4
ofstream out1, out2;
out1 = out2;               //错误:不能对流对象赋值
ofstream print(ofstream);  //错误:不能初始化ofstream参数
out2 = print(out2);        //错误:不能拷贝流对象

由于不能拷贝io对象,因此也不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

一个IO错误的例子:

1
2
int ival;
cin >> ival;

输入运算符期待读取一个int,但是得到一个字符B,这样就会错误。
一个流一旦错误,后续的IO操作都会失败。只有在流处于无错状态时,才能正常读写数据。
由于流可能处于错误状态,因此在使用前需要对其进行检查,最简单的方法是将其当作一个条件来使用:

1
2
while(cin >> word)
    // ok,读操作成功

while循环检查»表达式返回的流的状态。如果输入成功,流保持有效状态,则条件为真。

流对象的rdstate成员返回一个iostate值,对应流的当前状态。
setstate操作将给定条件位置位,表示发生对应错误。 clear成员是一个重载的成员:他有一个不接受版本的参数,而另一个版本即诶搜一个iostate类型的参数。clear不接受参数的版本清除所有错误标志为,执行clear()后,调用good会返回true。可以这样使用这些成员:

1
2
3
4
auto old_satate = cin.rdstate();
cin.clear();                      //使cin有效
process_input(cin);               //使用cin
cin.setstate(old_state);          //将cin置为原有状态

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

1
2
//复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~bin.badbit);

每个输出流都管理一个缓冲区,用来保存程序读写的数据。如果执行以下代码

1
os << "please enter a value:";

文本可能立即打印出来,也可能保存在缓冲区随后再打印。操作系统可以使用缓冲机制将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很好事,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
导致缓冲刷新的原因有很多:

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

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

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

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

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

如果程序异常终止,输出缓冲区不会被刷新,输出的数据可能停留在输出缓冲区中等待打印 因此在调试一个已经崩溃的程序时,需要确认输出的数据确实已经刷新了。

当一个输入流被关联到一输出流使,任何试图从输入流中读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起,因此
cin >> ival; 导致cout的缓冲区被刷新。
tie有两个重载的版本:一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o),将流x关联到输出流o。
既可以将一个istream对象关联到另一个ostream,也可以将一个ostreaim关联到另一个ostream。

1
2
3
4
5
6
cin.tie(&cout);
//old_tie指向当前关联到cin的流
ostream *old_tie = cin.tie(nullptr); //cin不再与其他流关联
// 将cin与cerr关联
cin.tie(&cerr);   //读取cin会刷新cerr
cin.tie(old_tie); //重建cin和cout之间的正常关联

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