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 outlout2
	outl = out2				//错误:不能对流对象赋值
	ofstream printofstream); //错误:不能初始化ofstream参数
    out2 = printout2);		  //错误:不能拷贝流对象
  • IO对象不能存在容器里.
  • 形参和返回类型也不能是流类型。
  • 形参和返回类型一般是流的引用
  • 读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

8.1.2 条件状态

状态 解释
strm:iostate 是一种机器无关的类型,提供了表达条件状态的完整功能
strm:badbit 用来指出流已经崩溃
strm:failbit 用来指出一个IO操作失败了
strm:eofbit 用来指出流到达了文件结束
strm:goodbit 用来指出流未处于错误状态,此值保证为零
s.eof() 若流seofbit置位,则返回true
s.fail() 若流sfailbit置位,则返回true
s.bad() 若流sbadbit置位,则返回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是一个流对象。

查询流的状态:

  1. 可根据while (cin >> word) ,将流作为条件使用,while 循环将检查 >> 表达式返回的流的状态。如果输入操作成功,流保持有效状态,则条件为真。
  2. 此外,我们还可以根据 goodbit, failbit, eofbit, badbit 来查询流的状态。badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位,流就无法再使用了。在发生可恢复错误后,failbit 被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。如果到达文件结束位置,eofbitfailbit都会被置位。goodbit的值为0,表示流未发生错误。如果badbitfailbiteofbit任一个被置位,则检测流状态的条件会失败。
  3. 标准库还定义了一组函数来查询这些标志位的状态。操作 good 在所有错误位均未置位的情况下返回 true ,而 badfaileof 则在对应错误位被置位时返回 true。此外,在 badbit 被置位时,fail 也会返回 true。这意味着,使用 goodfail 是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于 !fail() 。而 eofbad 操作只能表示特定的错误。

管理流的状态:

流对象的 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 的内容都是立即刷新的。
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cincerr 都关联到 cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。

刷新输出缓冲区:

  • endl:输出一个换行符并刷新缓冲区。
  • flush:刷新流,单不添加任何字符。
  • ends:在缓冲区插入空字符null,然后刷新。
  • unitbuf:告诉流接下来每次操作之后都要进行一次flush操作。
  • nounitbuf:回到正常的缓冲方式。

如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。

关联输入和输出流:

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将coutcin关联在一起,因此语句 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解除绑定,这样做了之后要注意不要同时混用coutprintf之类。在默认的情况下 cin 绑定的是 cout ,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)0表示空指针)来解除cincout的绑定,进一步加快执行效率。

#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操作。

指定文件模式有如下限制:

  • 只可以对ofstreamfstream对象设定out模式。只可以对ifstreamfstream对象设定in模式。
  • 只有当out也被设定时才可设定trunc模式。
  • 只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
  • 默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作(参见17.5.3节,第676页,将介绍对同一个文件既进行输入又进行输出的方法)。
  • atebinary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。

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:

  • istringstreamstring读取数据。
  • ostringstreamstring写入数据。
  • stringstream可以读写给定string

stringstream特有的操作

操作 解释
sstream strm 定义一个未绑定的stringstream对象
sstream strm(s) s初始化对象
strm.str() 返回strm所保存的string的拷贝
strm.str(s) s拷贝到strm中,返回void

上表中sstream是头文件sstream中任意一个类型。s是一个string