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
。