第八章 IO库
8.1 IO类
IO类型和头文件如下:
头文件 | 类型 |
---|---|
iostream | istream, wistream |
ostream, wostream | |
iostream, wiostream | |
fstream | ifstream, wifstream |
ofstream, wofstream | |
fstream, wfstream | |
sstream | istringstream, wistringstream |
ostringstream, wostringstream | |
stringstream, wstringstream |
如cin就是一个istream对象,用于从标准输入流中读取数据。
w开头的类型都是为了支持宽字符wchar_t类型的,对应的对象就是wcin,wcout和werr。
IO类型之间的关系
fstream和sstream中的类型都继承自iostream中的类型,比如ifstream和istringstream都是继承istream类型的,所以它们使用>>,<<,getline等操作符或方法都是可以的。
8.1.1 IO对象无拷贝或赋值
由于不能拷贝IO对象,所以也不能将形参或返回类型设置为流类型,原因是“形参”和“返回类型”都要利用到拷贝操作拷贝创建一个中间对象。
读写一个io对象是会改变其状态的,所以传递的值或者引用都不能加const。
8.1.2 条件状态
IO操作与生俱来的问题:可能发生错误。有的可恢复,有的则不可。
访问流状态和操纵的函数及标志有:
状态及函数 | 介绍 |
---|---|
strm::iostate | strm为一种IO类型。iostate则是一种机器相关的类型。 |
strm::badbit | 指出流已崩溃。通常是系统级的,无法恢复。 |
strm::failbit | 指出IO操作失败了。但通常可以恢复。 |
strm::eofbit | 指出流已到达了文件结束 |
strm::goodbit | 指出流未处于错误状态。此值保证为0 |
s.eof() | 若流的eofbit置位,则返回true |
s.fail() | 若流的failbit或badbit置位,则返回true |
s.bad() | 若流的badbit置位,则返回true |
s.good() | 若流处于有效状态,则返回true |
s.clear() | 将流中所有条件状态复位 |
s.clear(flags) | 将流中对应条件状态位复位。flags位iostate类型。 |
s.rdstate() | 返回流的当前状态,返回值为iostate类型。 |
s.setstate(flags) | 将流中对应条件状态位置位。flags位iostate类型。 |
把流当作条件使用是确定一个流对象的状态的最好方法,如while(cin >> word)。
查询流的状态
把流当作条件使用,只能知道它是否有效,而没法知道其具体发生了什么。IO库定义了一个与机器无关的iostate类型,提供了表达流状态的完整功能。
IO库提供了4个iostate类型的constexpr值表示特定的位模式。它们可以与位运算符一起使用来一次性检测或设置多个标志位。
badbit表示系统级错误,如不可恢复的读写错误。在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误,这种错误可以被修正,流仍可使用。
使用good或fail是确定流的总体状态的正确方法。实际上,把流当作条件就等价于用!fail()。而eof和bad只能表示特定错误。
管理条件状态
复位failbit和badbit,保持其他标志位不变:
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
8.1.3 管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。
os << "xxxxxxx"; 文本串可能立即打印出来也可能被操作系统保存在缓冲区中。
有了缓冲机制,操作系统可以将程序的多个输出操作组合成单一的系统级写操作。这是由于设备的写操作可能很耗时,因此把多个输出组合成一个再一次输出可以带来性能提升。
导致缓冲刷新的操作有很多:
程序正常结束,作为main函数的return操作的一部分,缓冲刷新执行。
缓冲区满。
用如endl的操纵符来显式刷新缓冲区。
-
每个输出操作后可以用操纵符unitbuf设置流的内部状态来清空缓冲区。默认对cerr是设置unitbuf的,所以写到cerr的内容都是立即输出。
cout << 'xx' << unitbuf; // 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf; //回到正常的缓冲方式
被关联到另一个流,此时,当读写被关联的流时,关联到的流的缓冲区都会被刷新。如cin和cerr都被关联到cout,因此读cin或写cerr都会导致cout缓冲区被刷新。
刷新输出缓冲区
IO库中还有两个类似的操纵符:flush和ends。flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。
cout << 'xx' << flush; cout << 'xx' << ends;
关联输入输出流
当一个输入流被关联到一个输出流上时,任何试图从输入流读取数据的操作都会刷新关联的输出流。标准库将cin和cout关联了起来,所以 cin >> xx;
时会导致cout的缓冲区被刷新。
交互式系统通常应该关联输入输出流。
tie有两个版本:
1.不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。
2.接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o)将流x关联到输出流o。
如 cin.tie(&cerr);
cin.tie(nullptr);
8.2 文件输入输出
8.2.1 使用文件流对象
在要求使用基类型对象的地方,我们可以用继承类型的对象来替代。如,接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用。
成员函数open和close
若定义了一个空文件流对象,可以随后再调用open将它与文件关联起来。
若open失败,failbit会被置位。因为open可能失败,所以进行open是否成功的检查是好习惯。
一旦一个文件流已经打开,它就保持与对应文件的关联。对一个已经打开的文件流调用open会失败,要先关闭才能关联到另外一个文件。
如果open成功,则open会设置流的状态,使得good()为true。
自动构造和析构
比如在循环内定义一个ifstream对象,每次循环,都会创建和销毁一次。当一个fstream对象离开其作用域时,与之关联的文件会自动关闭。
fstream对象在销毁时,close会自动被调用。
8.2.2 文件模式
每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。
文件模式 | |
---|---|
in | 以读方式打开 |
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
in只针对ifstream和fstream,out只针对ofstream和fstream。
只有当out也被设定时才可设定trunc模式;只要trunc没被设定,就可设定app模式。在app模式下,文件总以输出方式被打开。
默认情况下,没有指定trunc,以out模式打开的文件也会被截断。
ate和binary可用于任何类型的文件流对象,且可与其他任何文件模式组合使用。
与fstream关联的文件默认以in和out模式打开,ifstream以in,ofstream以out。
以out模式打开文件会丢弃已有数据
ofstream out("file1"); //隐含地以输出模式打开文件并截断文件
ofstream out1("file1", ofstream::out); //隐含地截断文件
ofstream out2("file1", ofstream::out | ofstream::trunc);
//以上三条代码,file1都被截断
ofstream app("file1", ofstream::app); //隐含为输出模式
ofstream app("file1", ofstream::out | ofstream::app);
每次调用open时都会确定文件模式
8.3 string流
sstream头文件定义了三个类型来支持内存IO。
stringstream特有的操作为:
strm.str() //返回strm保存的string的拷贝
strm.str(s) //将string s拷贝到strm中,返回void
8.3.1 使用istringstream
当我们的某些工作是对整行文本做处理,而其他一些工作是针对行内的单个单词时,就可使用istringstream。
如从 while (getline(cin, line))
先从标准输入流中读取行,
然后istringstream record(line);
把这一行绑定到record上,此时要操作行中的每一个单词就很容易了,用record >> word
就可以读取其中的单词。istringstream充分利用了输入流读取字符串时遇到空格就结束的特性,所以可以用来方便地读取一行中的单词。
8.3.2 使用ostringstream
当我们逐步构造输出,想最后一起打印时,oss是很有用的。
小结
- iostream处理控制台IO
- fstream处理命名文件IO
- stringstream完成内存string的IO
输入类都继承自istream,输出类都继承自ostream。因此,可以在istream对象上执行的操作也可以在ifstream或istringstream对象上执行。ostream同理。
每个IO对象都维护一组条件状态,用来指出此对象上是否可以进行IO操作。