IO库学习笔记,不是很全面。
一、IO类
先看一张表:
标准库通过继承机制来定义了一组面向对象类,这些由继承机制实现的类都享有共通的一些接口。因此,我们通常可以将一个派生类对象当做它的基类对象来使用。
由上图可以看出我们的IO库继承图。
标准库分别定义了三个独立的头文件:<iostream>、<fstream>、<sstream>
<iostream>定义了读写流的基本类型,一般从窗口读写;<fstream>定义了读写命名文件的类型;<sstream>定义了读写内存string对象的类型。而且<fstream>中的每种类型和<sstream>中定义的类型都是从<iostream>头文件中定义的相关类型派生而来的。
为了支持宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据、例如:wcin、wcout、wcerr是分别对应cin、cout、cerr的宽字符版本,宽字符版本对象和类型与其对应的char类型版本定义在同一个头文件中。例如,fstream头文件定义了ifstream和wifstream类型。
1. IO对象无拷贝或赋值
ofstream out1, out2;
out1 = out2; // 错误,不能对流对象赋值
ofstream print(ofstream); // 错误,不能初始化流参数
out2 = print(out2); // 错误,不能拷贝流对象
所以我们一般都是使用流对象的引用或指针来操作流对象。
2. 条件状态
strm::iostate |
机器相关的整型名,由各个iostream类定义,用于定义条件状态 |
strm::badbit |
strm::iostate类型的值,用于指出被破坏的流[1,Linux32位] |
strm::failbit |
strm::iostate类型的值,用于指出失败的IO操作[4] |
strm::eofbit |
strm::iostate类型的值,用于指出流已经到达文件结束符[2] |
s.eof() |
如果设置了流 s的 eofbit值,则该函数返回true |
s.fail() |
如果设置了流 s的 failbit值或badbit,则该函数返回true |
s.bad() |
如果设置了流 s的 badbit值,则该函数返回true |
s.good() |
如果流 s处于有效状态,则该函数返回true |
s.clear() |
将流 s中的所有状态值都重设为有效状态 |
s.clear(flag) |
将流 s中的某个指定条件状态设置为有效。flag的类型是strm::iostate |
s.setstate(flag) |
给流 s添加指定条件。flag的类型是strm::iostate |
s.rdstate() |
返回流 s的当前条件,返回值类型为strm::iostate |
int ival;
cin >> ival;
假如我们输入Boo,该操作就会失败,因为我们期待输入一个int,而输入的却是一个字符B。这样cin就会进入错误状态。类似的,如果我们输入文件结束标识,cin也会进入错误状态。一个流一旦进入错误状态,后续操作都会失败。
确定一个流对象的状态最简单的办法是将它作为一个条件使用:
while (cin >> ival)
//
如果cin操作没有失败,我们就可以进行后续操作。查询流状态:
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能:
badbit表示系统级错误,如不可恢复的读写错误,通常情况下,一旦badbit被置位,流就无法再使用了。在发生可恢复的错误后,failbit被置位,如期望读取数值却读出一个字符的错误。如果到达文件尾,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。badbit、failebit和eofbit任一个被置位,则检测流状态的条件会失败。
标准库还定义了一组函数来查询这些标志位的状态。good函数在所有错误位均未被置位时返回true,而bad、fail和eof函数则对应错误位被置位时返回true。此外,在badbit被置位时,fail也会返回true。
管理条件状态:
流对象rfstate()成员返回一个iostate值,对应流当前状态。setstate操作将给定的条件位置位,表示发生了对应错误。clear成员是一个重载的成员:它有一个不接受参数的版本,另一个是接受一个iostate类型的参数。
clear不接受参数的版本清除(复位)所有错误标志位,执行clear()后,调用good()会返回true,可以这样使用这些成员:
istream::iostate old_state = cin.rdstate(); // 记住cin当前状态
cin.clear(); // 使cin有效,good()返回true
process_input(cin); // 使用cin
cin.setstate(old_state); // 将cin置为原有状态
带参数的clear版本接受一个iostate值,表示流的新状态。为了复位单一的条件状态位,首先用rdstate()读出当前条件状态,然后用位操作将所需位复位来生成新的状态:
// 复位failbit和badbit,保持其他位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
3. 管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据:
os << "please enter a value: ";
这个字符串可能立即打印出来,也可能被操作系统保存在缓冲区,随后再打印。
导致缓冲区刷新(即,数据真正的写到输出设备或文件)的原因有很多:
(1). 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行;
(2). 缓冲区满时,需要撒互信缓冲,而后新的数据才能继续写入缓冲区;
(3). 使用操纵符如endl来显示刷新缓冲区;
(4). 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的;
(5). 一个输出流可能被关联(tie)到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,cin和cerr都关联到cout,因此,读cin或写cerr时都会导致cout的缓冲区被刷新。
刷新输出缓冲区:
我们已经知道了endl操纵符,它完成换行并刷新缓冲区,IO库还定义了两个类似的操纵符:flush和ends。flush刷新缓冲区,但不输出任何字符,ends向缓冲区插入一个空字符,然后刷新缓冲区。
cout << "hi!" << endl; // 输出hi!和一个换行,然后刷新缓冲区
cout << "hi!" <<flush; //输出flush,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" <<; //输出hi!和一个空字符,然后刷新缓冲区
unitbuf操纵符:如果想在每次输出操作后都刷新缓冲区,可以使用unitbuf。它告诉流在接下来的每次写操作后都进行一次flush操作,而nounitbuf操作符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。
cout << unitbuf; // 所有的输出操作后都会立即刷新缓冲区
/*
*/
cout << nounibuf; //回到正常的缓冲方式
警告:如果程序崩溃,输出缓冲区不会被刷新
程序异常终止,输出缓冲区是不会刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
所以,当调试一个已经崩溃了的程序时,必须确认那些你认为已经输出的数据确实输出了。否则,可能会将大量的程序浪费在追踪代码为什么没有执行,而实际上只是程序崩溃后缓冲区没被刷新而已。
关联输入和输出流:
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流,标准库将cout与cin绑在一起,因此语句:
cin >> ival;
导致cout的缓冲区被刷新。
交互式系统通常来说应该将输入和输出流关联起来,这意味着,所有的输出包括用户提示信息,都会在读操作之前被打印出来。
tie有两个重载的版本:一个版本不带参数,返回指向输出流的指针,如果本对象当前关联到一个输出流,则返回的就是指向这个输出流的指针。如果对象未关联到输出流,则返回空指针。第二个版本接受一个指向ostream对象的指针,将自己关联到这个ostream对象。
std::ostream* tie() const; // 不带参数版本,返回指向当前绑定的输出流的指针,若未关联到输出流,返回空指针
std::ostream* tie(std::ostream* tiestr); // 将tiestr指向的输出流绑定到该对象,并返回上一个绑定的指针
可以将一个istream对象关联到一个ostream对象,也可以关联到另一个ostream对象:
cin.tie(&cout);
ostream *old_tie = cin.tie(nullptr); // cin不再与其他流关联
cin.tie(&cerr); //将cin与cerr绑定,读取cin会刷新cerr而不是cout
cin.tie(old_tie); //将cin与cout重新绑定
为了彻底解开流的关联,我们可以传递一个空指针nullptr。每个流同时最多关联到一个流,但多个流可以同时关联到一个ostream。