0%

C++ Primer 第八章

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
2
3
4
ofstream out1, out2;
out1 = out2; //错误:不能对流对象賦值
ofstream print (ofstream) ; //错误:不能初始化ofstream参数
out2 = print (out2) ; //错误:不能拷贝流对象

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

条件状态

一个流发生错误,后续的IO操作都会失败,所以因通过代码检查它是否处于良好状态:

1
2
3
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
2
3
4
5
//记住cin的当前状态
auto old_state = cin.rdstate(); //记住cin的当前状态
cin.clear(); //使cin有效
process_input (cin) ; //使用cin
cin.setstate(old_state) ; //将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
2
3
cout <<"hi! " <<endl;			//输出hi和一个换行,然后刷新缓冲区
cout << "hi ! " << flush; //输出hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi ! " <<ends; //输出hi和一个空字符,然后刷新缓冲区

unitbuf操纵符

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

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

如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
当调试一个已经崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量时间浪费在追踪代码为什么没有执行上,而实际上代码已经执行了,只是程序崩溃后缓冲区没有被刷新,输出数据被挂起没有打印而已。

关联输入和输出流

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和 cin关联在一起,因此下面语句导致cout的缓冲区被刷新。
cin >> ival;
tie可以将自己和一个输出流绑定起来,并且返回与自己关联的对象:

1
2
3
4
5
6
cin.tie ( &cout) ;						//仅仅是用来展示:标准库将cin和cout关联在一起
// old_tie指向当前关联到cin的流(如果有的话)
ostream *old_tie = cin.tie(nullptr); //cin 不再与其他流关联
//将cin 与cerr关联;这不是一个好主意,因为cin应该关联到cout
cin.tie ( &cerr); //读取cin会刷新cerr而不是cout
cin.tie(old_tie) ; //重建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
2
ifstream in (ifile);						//构造一个ifstream并打开给定文件
ofstream out; //输出文件流未关联到任何文件

输入流in,初始化为从文件读取数据,输出流out,未关联。

用fstream代替iostream&

根据在要求使用基类对象的地方,我们可以使用继承类型的对象来替代。所以在调用一个具有iostream的参数时,可以用fstream来调用。

成员函数open和close