IO库
以往用到的IO库设施:
istream(输入流)类型,提供输入操作。
ostre am(输出流)类型,提供输出操作。
cin, 一个istream对象,从标准输入读取数据。
cout,一个ostream对象,向标准输出写入数据。
cerr,一个ostream对象,通常用于输出程序错误消息,写入到标准错误。
>>
运算符,用来从一个istream对象读取输入数据。<<
运算符,用来向一个ostream对象写入输出数据。getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中。
IO类
头文件 | 类型 |
---|---|
iostream | istream, wistream从流读取数据 ostream, wost ream向流写入数据 iostream,wiostream读写流 |
fstream | ifstream, wifstream 从文件读取数据 ofstream, wofstream 向文件写入数据 fstream,wfstream读写文件 |
sstream | istringstream, wistringstream 从string读取数据. ostringstream, wostringstream 向string写入数据 stringstream, wstringstream 读写string |
为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t 类型的数据。宽字符版本的类型和函数的名字以一个w开始。例如,wcin、wcout和wcerr是分别对应cin、cout和cerr的宽字符版对象。宽字符版本的类型和对象与其对应的普通char版本的类型定义在同一个头文件中。例如,头文件fstream定义了ifstream 和wifstream类型。
IO类型间的关系
设备类型和字符大小不会影响我们执行的IO操作,例如使用>>读取数据,我们不需要管是从控制台还是磁盘文件、还是string读取,同样也不需要管字符存入的是char还是wchar_t。这一点实际上是通过类的继承机制实现的。类型ifstream和istringstream都继承自istream. 因此,我们可以像使用istream对象-样来使用ifstream和istringstream对象。
IO对象无拷贝或赋值
1 | ofstream out1, out2; |
由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
条件状态
一个流发生错误,后续的IO操作都会失败,所以因通过代码检查它是否处于良好状态:
1 | while(cin >> word){ |
查询流的状态
上面的操作只知道有没有错误,但不知道是什么错误,所以IO库定义了一个与机器无关的iostate类型:
代码 | 说明 |
---|---|
badbit | 系统级错误,如不可恢复的读写错误。 |
failbit | 期望读取的数值却读出一个字符等错误,这种问题可以修正,且流还可以使用。 到达文件结束位置会被置位。 |
eofbit | 到达文件结束位置,其会被置位。 |
goodbit | 值为0表示流未发生错误,如果上面任何一个被置位,则检测流状态的条件会失败 |
标准库还定义了一组函数来查询这些标志位的状态。操作 good在所有错误位均未置位的情况下返回true,而 bad、fail和 eof则在对应错误位被置位时返回true。此外,在 badbit被置位时,fail也会返回true。这意味着,使用good或fail是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()。而eof和 bad操作只能表示特定的错误。
管理条件状态
流对象的rdstate成员返回一个iostate值,对应流的当前状态。setstate操作将给定条件位置位,表示发生了对应错误。clear成员是一个重载的成员(参见6.4节,第206页):它有一个不接受参数的版本,而另一个版本接受一个iostate类型的参数。
clear不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good会返回true。我们可以这样使用这些成员:
1 | //记住cin的当前状态 |
带参数的clear版本接受一个iostate值,表示流的新状态。为了复位单一的条件状态位,我们首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。例如,下面的代码将failbit和 badbit复位,但保持eofbit不变://复位failbit和badbit,保持其他标志位不变cin.clear(cin.rdstate () & ~cin.failbit & ~cin.badbit);
管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如,如果执行下面的代码
os << "please enter a value: ";
文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多;
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
- 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
- 我们可以使用操纵符如 endl(参见1.2节,第6页)来显式刷新缓冲区。
- 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
- 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和 cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。
刷新输出缓冲区
1 | cout <<"hi! " <<endl; //输出hi和一个换行,然后刷新缓冲区 |
unitbuf操纵符
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作之后都进行一次 flush 操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
1 | cout << unitbuf; //所有输出操作后都会立即刷新缓冲区 |
如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
当调试一个已经崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量时间浪费在追踪代码为什么没有执行上,而实际上代码已经执行了,只是程序崩溃后缓冲区没有被刷新,输出数据被挂起没有打印而已。
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和 cin关联在一起,因此下面语句导致cout的缓冲区被刷新。cin >> ival;
tie可以将自己和一个输出流绑定起来,并且返回与自己关联的对象:
1 | cin.tie ( &cout) ; //仅仅是用来展示:标准库将cin和cout关联在一起 |
在这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了tie。为了彻底解开流的关联,我们传递了一个空指针。每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。
文件输入输出
头文件 fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。
fstream中定义的类型还增加了一些新的成员管理与流关联的文件
代码 | 说明 |
---|---|
fstream fstrm; | 创建一个未绑定的文件流。fstream是头文件fstream 中定义的一个类型 |
fstream fstrm (s); | 创建一个fstream,并打开名为s的文件。s可以是string类型,或者是 一个指向C风格字符串的指针。这些构造函数都是explicit的。默认的 文件模式mode依赖于fstream的类型 |
fstream fstrm (s,mode) ; |
与前一个构造函数类似,但按指定mode打开文件 |
fstrm.open (s) | 打开名为s 的文件,并将文件与 fstrm绑定。s可以是一个string或一个指向 C风格字符串的指针。默认的文件mode依赖于fstream的类型。返回void |
fstrm.close () | 关闭与fstrm绑定的文件。返回void |
fstrm.is_open () | 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭 |
使用文件流对象
向读写一个文件时,需要先定义一个文件流对象,如果提供了一个文件名,则会自动调用open:
1 | ifstream in (ifile); //构造一个ifstream并打开给定文件 |
输入流in,初始化为从文件读取数据,输出流out,未关联。
用fstream代替iostream&
根据在要求使用基类对象的地方,我们可以使用继承类型的对象来替代。所以在调用一个具有iostream的参数时,可以用fstream来调用。