C++ 语言的 IO 包括标准 IO、文件 IO 以及内存IO(即从 string 读取数据,从 string 写入数据)。
8.1 IO 类
标准库定义的 IO 类型有:
| 头文件 | 作用 | 类型 |
|---|---|---|
| iostream | 定义了用于读写流的基本类型 | istream, ostream, iostream |
| fstream | 定义了读写命名文件的类型 | ifstream, ofstream, fstream |
| sstream | 定义了读写内存 string 的基本类型 |
isringstream, osringstream, stringstream |
8.1.1 IO对象无拷贝或赋值
ofstream outl,out2;
outl = out2; //错误:不能对流对象赋值
ofstream print(ofstream); //错误:不能初始化ofstream参数
out2 = print(out2); //错误:不能拷贝流对象
- IO对象不能存在容器里.
- 形参和返回类型也不能是流类型。
- 形参和返回类型一般是流的引用。
- 读写一个IO对象会改变其状态,因此传递和返回的引用不能是
const的。
8.1.2 条件状态
| 状态 | 解释 |
|---|---|
strm:iostate |
是一种机器无关的类型,提供了表达条件状态的完整功能 |
strm:badbit |
用来指出流已经崩溃 |
strm:failbit |
用来指出一个IO操作失败了 |
strm:eofbit |
用来指出流到达了文件结束 |
strm:goodbit |
用来指出流未处于错误状态,此值保证为零 |
s.eof() |
若流s的eofbit置位,则返回true |
s.fail() |
若流s的failbit置位,则返回true |
s.bad() |
若流s的badbit置位,则返回true |
s.good() |
若流s处于有效状态,则返回true |
s.clear() |
将流s中所有条件状态位复位,将流的状态设置成有效,返回void |
s.clear(flags) |
将流s中指定的条件状态位复位,返回void |
s.setstate(flags) |
根据给定的标志位,将流s中对应的条件状态位置位,返回void |
s.rdstate() |
返回流s的当前条件状态,返回值类型为strm::iostate |
上表中,strm是一种 IO 类型(如istream), s是一个流对象。
查询流的状态:
- 可根据
while (cin >> word),将流作为条件使用,while循环将检查>>表达式返回的流的状态。如果输入操作成功,流保持有效状态,则条件为真。 - 此外,我们还可以根据
goodbit,failbit,eofbit,badbit来查询流的状态。badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了。在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。 - 标准库还定义了一组函数来查询这些标志位的状态。操作
good在所有错误位均未置位的情况下返回true,而bad、fail和eof则在对应错误位被置位时返回true。此外,在badbit被置位时,fail也会返回true。这意味着,使用good或fail是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()。而eof和bad操作只能表示特定的错误。
管理流的状态:
流对象的 rdstate 成员返回一个 iostate 值,对应流的当前状态。
setstate操作将给定条件位置位,表示发生了对应错误。
//记住cin的当前状态
auto old_state = cin.rdstate(); //记住cin的当前状态
cin.clear(); //使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //将cin 置为原有状态
8.1.3 管理输出缓冲
每个输出流都管理一个缓冲区,执行输出的代码,文本串可能立即打印出来,也可能被操作系统保存在缓冲区内,随后再打印。
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:
- 程序正常结束,作为
main函数的return操作的一部分,缓冲刷新被执行。 - 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
- 我们可以使用操纵符如
endl来显式刷新缓冲区。 - 在每个输出操作之后,我们可以用操纵符
unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。 - 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,
cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。
刷新输出缓冲区:
endl:输出一个换行符并刷新缓冲区。flush:刷新流,单不添加任何字符。ends:在缓冲区插入空字符null,然后刷新。unitbuf:告诉流接下来每次操作之后都要进行一次flush操作。nounitbuf:回到正常的缓冲方式。
如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
关联输入和输出流:
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起,因此语句 cin >> val 将会导致 cout 的缓冲区刷新。
tie 有两个重载的版本,一个版本不带参数,返回指向输出流的指针。如果对象关联到了一个输出流,则返回这个流的指针,如果对象未关联到一个输出流,则返回一个空指针。tie 的第二个版本接受一个指向 ostream 的指针,将自己关联到此 ostream。
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 间的正常关联
故因此在ACM竞赛中,我们常常这样操作来使得输入加速,在IO之前将stdio解除绑定,这样做了之后要注意不要同时混用cout和printf之类。在默认的情况下 cin 绑定的是 cout ,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示空指针)来解除cin与cout的绑定,进一步加快执行效率。
#include <iostream>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
// IO
}
8.2 文件输入输出
-
头文件
fstream定义了三个类型来支持文件IO:
ifstream从一个给定文件读取数据。ofstream向一个给定文件写入数据。fstream可以读写给定文件。
-
文件流:需要读写文件时,必须定义自己的文件流对象,并绑定在需要的文件上。
fstream特有的操作:
| 操作 | 解释 |
|---|---|
fstream fstrm; |
创建一个未绑定的文件流。 |
fstream fstrm(s); |
创建一个文件流,并打开名为s的文件,s可以是string也可以是char指针 |
fstream fstrm(s, mode); |
与前一个构造函数类似,但按指定mode打开文件 |
fstrm.open(s) |
打开名为s的文件,并和fstrm绑定 |
fstrm.close() |
关闭和fstrm绑定的文件 |
fstrm.is_open() |
返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭 |
上表中,fstream是头文件fstream中定义的一个类型,fstrm是一个文件流对象。
8.2.2 文件模式
| 文件模式 | 解释 |
|---|---|
in |
以读的方式打开 |
out |
以写的方式打开 |
app |
每次写操作前均定位到文件末尾 |
ate |
打开文件后立即定位到文件末尾 |
trunc |
截断文件 |
binary |
以二进制方式进行IO操作。 |
指定文件模式有如下限制:
- 只可以对
ofstream或fstream对象设定out模式。只可以对ifstream或fstream对象设定in模式。 - 只有当
out也被设定时才可设定trunc模式。 - 只要
trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。 - 默认情况下,即使我们没有指定
trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作(参见17.5.3节,第676页,将介绍对同一个文件既进行输入又进行输出的方法)。 ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
以 out 模式打开文件会丢弃已有数据
默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式:
//在这几条语句中,filel都被截断
ofstream out("filel"); //隐含以输出模式打开文件并截断文件ofstream out2("file1",ofstream::out);//隐含地截断文件
ofstream out3("file1",ofstream::out | ofstream::trunc);
//为了保留文件内容,我们必须显式指定app模式
ofstream app("file2",ofstream::app);//隐含为输出模式
ofstream app2("file2",ofstream::out | ofstream::app);
每次调用open 时都会确定文件模式
对于一个给定流,每当打开文件时,都可以改变其文件模式。
ofstream out; //未指定文件打开模式
out.open("scratchpad"); //模式隐含设置为输出和截断
out.close(); //关闭out,以便我们将其用于其他文件out.open("precious",ofstream::app); //模式为输出和追加
out.close();
8.3 String 流
头文件sstream定义了三个类型来支持内存IO:
istringstream从string读取数据。ostringstream向string写入数据。stringstream可以读写给定string。
stringstream特有的操作
| 操作 | 解释 |
|---|---|
sstream strm |
定义一个未绑定的stringstream对象 |
sstream strm(s) |
用s初始化对象 |
strm.str() |
返回strm所保存的string的拷贝 |
strm.str(s) |
将s拷贝到strm中,返回void |
上表中sstream是头文件sstream中任意一个类型。s是一个string。