标准IO库
C++使用标准库类处理输入和输出:
- iostream类处理面向流的输入和输出
- fstream类处理已命名文件的IO
- stringstream类处理内存中字符串的IO
所有这些类都是通过继承相互关联的。输入类继承了istream,输出类继承了ostream。因此,可在istream对象上执行的操作同样适用于ifstream或istringstream对象。而继承ostream的输出类也是类似的。
所有IO对象都有一组条件状态,用来指示是否可以通过该对象进行IO操作。如果出现了错误(例如遇到文件结束符)对象的状态将标志无法再进行输入,直到修正了错误为止。标准库提供了一组函数设置和检查这些状态。
面向对象的标准库
-
C++中简单的iostream继承层次
-
IO对象不可复制或赋值
ofstream out1, out2; out1 = out2; //error,不能赋值 ofstream print(ofstream); out2 = print(out2); //error,不能复制
这个要求的两层含义:
-
由于流对象不能复制,因此不能存储在vector(或其他)容器中
-
形参或返回类型也不能为流类型。如果需要返回或传递IO对象,则必须传递或返回指向该对象的指针或引用:
ofstream &print(ofstream&); while (print(out2)) {/*...*/} //一般对IO对象的读写会改变它的状态,因此引用必须是非const的
-
条件状态
IO 标准库管理一系列条件状态(condition state)成员,用来标记给定的 IO 对象是否处于可用状态,或者碰到了哪种特定的错误。下表列出了标准库定义的一组函数和标记,提供访问和操纵流状态的手段。
条件状态 | 作用 |
---|---|
strm::iostate | 机器相关的整型名,由各个 iostream 类定义,用于定义条件状态 |
strm::badbit | strm::iostate 类型的值,用于指出被破坏的流 |
strm::failbit | strm::iostate 类型的值,用于指出失败的 IO 操作 |
strm::eofbit | strm::iostate 类型的值,用于指出流已经到达文件结束符 |
s.eof() | 如果设置了流 s 的 eofbit 值,则该函数返回 true |
s.fail() | 如果设置了流 s 的 failbit 值,则该函数返回 true |
s.bad() | 如果设置了流 s 的 badbit 值,则该函数返回 true |
s.good() | 如果流 s 处于有效状态,则该函数返回 true |
s.clear() | 将流 s 中的所有状态值都重设为有效状态 |
s.clear(flag) | 将流 s 中的某个指定条件状态设置为有效。flag 的类型是strm::iostate |
s.setstate(flag) | 给流 s 添加指定条件。flag 的类型是 strm::iostate |
s.rdstate() | 返回流 s 的当前条件,返回值类型为 strm::iostate |
流必须处于无错误状态,才能用于输入或输出。检测流是否用的最简单的方法是检查其真值:
if (cin) // ok to use cin, it is in a valid state
while (cin >> word) // ok: read operation successful ...
//if 语句直接检查流的状态,而 while 语句则检测条件表达式返回的流,从而间接地检查了流的状态。如果成功输入
//条件检测为 true。
-
如何访问或控制流的状态,以便知道是达到了文件结尾,还是遇到了IO设备上的错误
流的状态由 bad、fail、eof 和 good 操作提示。如果 bad、fail 或者 eof中的任意一个为 true,则检查流本身将显示该流处于错误状态。类似地,如果这三个条件没有一个为 true,则 good 操作将返回 true。
clear 和setstate 操作用于改变条件成员的状态。clear 操作将条件重设为有效状态。在流的使用出现了问题并做出补救后,如果我们希望把流重设为有效状态,则可以调用 clear 操作。使用 setstate 操作可打开某个指定的条件,用于表示某个问题的发生。除了添加的标记状态,setstate 将保留其他已存在的状态变量不变。(也就是出了问题时用来设置状态的)
-
流状态的查询和控制
int ival; //这个循环不断读入 cin,直到到达文件结束符或者发生不可恢复的读取错误为止。 while (cin >> ival, !cin.eof()) { if (cin.bad()) // input stream is corrupted; bail out throw runtime_error("IO stream corrupted"); if (cin.fail()) { // bad input cerr<< "bad data, try again"; // warn the user cin.clear(istream::failbit); // reset the stream continue; // get next input } // ok to process ival }
-
条件状态的访问
使用rdstate成员函数,它返回一个iostate类型的值,对应流当前的整个条件状态:
// remember current state of cin istream::iostate old_state = cin.rdstate(); cin.clear(); process_input(); // use cin cin.clear(old_state); // now reset cin to old state
-
多种状态的处理
通过多次调用 setstate 或者 clear 函数实现。
is.setstate(ifstream::badbit | ifstream::failbit)
另外一种方法则是使用按位或(OR)操作符在一次调用中生成“传递两个或更多状态位”的值。按位或操作使用其操作数的二进制位模式产生一个整型数值。对于结果中的每一个二进制位,如果其值为 1,则该操作的两个操作数中至少有一个的对应二进制位是 1。
is.badbit | is.failbit
输出缓冲区的管理
每个IO 对象管理一个缓冲区,用于存储程序读写的数据;
下面几种情况将导致缓冲区的内容被刷新
,即写入到真实的输出设备或者文件:
- 程序正常结束。作为 main 返回工作的一部分,将清空所有输出缓冲区。
- 在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新。
- 用
操纵符
显式地刷新缓冲区,例如行结束符 endl。 - 在每次输出操作执行完后,用
unitbuf 操作符
设置流的内部状态,从而清空缓冲区。 - 可将
输出流与输入流关联(tie
)起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区
-
输出缓冲区的刷新
cout << "hi!" << flush; // 用于刷新流,但不在输出中添加任何字符 cout << "hi!" << ends; // 在缓冲区中插入空字符 null,然后后刷新它 cout << "hi!" << endl; // 输出一个换行符并刷新缓冲区
-
unitbuf 操纵符
如果需要刷新所有输出,最好使用
unitbuf
操纵符。这个操纵符在每次执行完写操作后都刷新流:cout << unitbuf << "first" << " second" << nounitbuf; //等价于 cout << "first" << flush << " second" << flush; //nounitbuf操纵符将流恢复为使用正常的、由系统管理的缓冲区刷新方式。
注意:如果程序崩溃了,则不会刷新缓冲区
输出时应多使用 endl 而非 ‘\n’。使用endl 则不必担心程序崩溃时输出是否悬而未决(即还留在缓冲区,未输出到设备中)。
-
将输入和输出绑在一起(tie)
当输入流与输出流绑在一起时,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区,可以保证任何输出,包括给用户的提示,都在试图读之前输出,
//tie 函数可用 istream 或 ostream 对象调用,使用一个指向 ostream 对象的指针形参。调用 tie 函 //数时,将实参流绑在调用该函数的对象上。如果一个流调用 tie 函数将其本身绑在传递给 tie 的 ostream //实参对象上,则该流上的任何 IO 操作都会刷新实参所关联的缓冲区。 cin.tie(&cout); // illustration only: the library ties cin and cout for us ostream *old_tie = cin.tie(); //返回指向绑定的输出流的指针。 cin.tie(0); // break tie to cout, cout no longer flushed when cin is read 解除绑定 cin.tie(&cerr); // ties cin and cerr, not necessarily a good idea! // ... cin.tie(0); // break tie between cin and cerr 解除绑定 cin.tie(old_tie); // restablish normal tie between cin and cout //一个 ostream 对象每次只能与一个 istream 对象绑在一起。如果在调用tie 函数时传递实参 0,则打破该 //流上已存在的捆绑。
文件的输入和输出
-
文件流对象的使用
需要读写文件时,必须定义自己的对象,并将它们绑定在需要的文件上
ifstream infile(ifile.c_str()); //将输入流对象infile绑定到文件ifile ofsrteam outfile(ofile.c_str()); //将输出流对象outfile绑定到文件ofile //在使用fstream对象之前,还必须使这些对象捆绑要读写的文件,调用open成员函数即可 infile.open("in"); outfile.open("out")
-
检查文件是否打开成功
if(!infile) { cerr << "error: unable to open input file:" << infile <<endl; return -1; }
-
将文件流与新文件重新捆绑
需要先关闭(close)现在的文件,然后打开(open)另一个文件:
ifstream infile("in"); infile.close(); infile.open("next");
-
清除文件流的状态
ifstream input; //将input定义到while之外避免每次循环都创建新的流对象 vector<string>::const_iterator it = files.begin(); //vector的元素为文件名 while (it != files.end()) { input.open(it->c_str()); //打开文件 if(!input) break; while (input >> s) process(s); input.close(); //当处理完该文件,关闭 input.clear(); //重新恢复流的状态,等同于重新创建了该对象,这样就可以重用已存在的流对象 ++it; }
注意
:如果需要重用文件流读写多个文件,必须在读另一个文件之前调用clear清除该流的状态,以免之前的错误信息影响下一次input操作
-
-
文件模式
in 打开文件做读操作 out 打开文件做写操作 app 在每次写之前找到文件尾 ate 打开文件后立即将文件定位到文件尾 trunc 打开文件时清空已存在的文件流 binary 以二进制模式进行IO操作 out、trunc、app模式只能用于指定与ofstream或fstream对象关联的文件;
in模式只能用于指定与ifstream或fstream对象关联的文件;
所有文件都能以ate和binary模式打开;
为ofstream对象指定out模式等效于同时指定了out和trunc模式;
对于用ofstream打开的文件,要保存文件中已存在的数据,唯一方法时显式指定app模式打开
-
同一个文件作输入和输出运算
fstream对象既可以读也可以写它所关联的文件
fstream inOut("copyOut", fstream::in | fstream::out);
-
模式是文件的属性而不是流的属性
只要调用open函数,就要设置文件模式,其模式的设置可以是显式的,也可以是隐式的。如果没有指定文件模式,将使用默认值。
-
打开模式的组合
out 打开文件做写操作,删除文件中已有的数据 out | app 打开文件做写操作,在文件尾写入 out | trunc 与out模式相同 in 打开文件做读操作 in | out 打开文件做读、写操作,并定位于文件开头处 in | out | trunc 打开文件做读、写操作,删除文件中已有的数据 -
打开并检查输入文件的程序
ifstream& open_file(ifstream &in, const string &file){ in.close(); //关闭以存在的文件流 in.clear(); //清除错误信息 //由于不清楚in当前状态,首先调用close和clear将这个流设置为有效状态 in.open(file.c_str()); return in; }
-
字符串流
三种类型:
- istringstream, 由istream派生而来,提供读string的功能
- ostringstream, 由ostream派生而来,提供写string的功能
- stringstream, 由iostream派生而来,提供读写string的功能
stringstream strm; | 创建自由的stringstream对象 |
---|---|
stringstream strm(s); | 创建存储s的副本的stringstream对象,其中s是string类型的对象 |
strm.str() | 返回strm中存储的string类型对象 |
strm.str(s) | 将string类型的s复制给strm,返回void |
-
stringstream对象的使用
结合string输入操作符和getline函数,将istringstream对象与所读取的行绑定起来,这样只需使用普通的string输入操作符即可读出每行中的单词:
string line, word; while (getline(cin, line)) { //从输入缓冲区读取一行赋给line //处理每行 istringstream stream(line); //将line对象与stream绑定,读取line对象 while (stream >> word) { //从line对象中读取单个单词 //processing } }