8.1. 面向对象的标准库
IO 类型在三个独立的头文件中定义:iostream 定义读写控制窗口的类型,fstream 定义读写已命名文件的类型,而 sstream 所定义的类型则用于读写存储在内存中的 string 对象。在 fstream 和 sstream 里定义的每种类型都是从 iostream 头文件中定义的相关类型派生而来。
Header | Type |
---|---|
iostream |
istream 从流中读取
ostream 写到流中去
iostream 对流进行读写;从 istream 和 ostream 派生而来 |
fstream |
ifstream 从文件中读取;由 istream 派生而来
ofstream 写到文件中去;由 ostream 派生而来
fstream 读写文件;由 iostream 派生而来 |
sstream |
istringstream 从 string 对象中读取;由 istream 派生而来
ostringstream 写到 string 对象中去;由 ostream 派生而来
stringstream 对 string 对象进行读写;由 iostream 派生而来 |
迄今为止,所描述的流类(stream class)读写的是由 char 类型组成的流。此外,标准库还定义了一组相关的类型,支持 wchar_t 类型。每个类都加上“w ”前缀 ,以此与 char 类型的版本区分开来 。于是,wostream 、wistream 和 wiostream 类型从控制窗口读写 wchar_t 数据。相应的文件输入输出类是 wifstream 、wofstream 和 wfstream 。而 wchar_t 版本的 string 输入/输出流则是 wistringstream 、wostringstream 和 wstringstream 。标准库还定义了从标准输入输出读写宽字符的对象。这些对象加上“w ”前缀,以此与 char 类型版本区分:wchar_t 类型的标准输入对象是 wcin ;标准输出是 wcout ;而标准错误则是 wcerr 。
IO 对象不可复制或赋值
这个要求有两层特别重要的含义。正如在第九章 看到的,只有支持复制的元素类型可以存储在 vector 或其他容器类型里 。由于流对象不能复制 ,因此不能存储在 vector (或其他)容器中(即不存在存储流对象的 vector 或其他容器) 。
第二个含义是:形参或返回类型也不能为流类型 。如果需要传递或返回 IO 对象,则必须传递或返回指向该对象的指针或引用:
8.2. 条件状态
实现 IO 的继承正是错误发生的根源。一些错误是可恢复的;一些错误则发生在系统底层,位于程序可修正的范围之外。IO 标准库管理一系列条件状态(condition state) 成员,用来标记给定的 IO 对象是否处于可用状态 ,或者碰到了哪种特定的错误。表 8.2 列出了标准库定义的一组函数和标记,提供访问和操纵流状态的手段。
表 8.2. 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 语句直接检查流的状态,而 while 语句则检测条件表达式返回的流,从而间接地检查了流的状态。如果成功输入,则条件检测为 true 。
int ival; // read cin and test only for EOF; loop is executed even if there are other IO failures 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 }
8.3. 输出缓冲区的管理
系统将字符串字面值存储在与流 os 关联的缓冲区中。下面几种情况将导致缓冲区的内容被刷新,即写入到真实的输出设备或者文件:
程序正常结束 。作为 main 返回工作的一部分,将清空所有输出缓冲区。
在一些不确定的时候,缓冲区可能已经满了 ,在这种情况下,缓冲区将会在写下一个值之前刷新。
用操纵符(第 1.2.2 节 )显式地刷新缓冲区,例如行结束符 endl 。
在每次输出操作执行完后,用 unitbuf 操作符设置流的内部状态,从而清空缓冲区 。
可将输出流与输入流关联(tie )起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区 。
我们的程序已经使用过 endl 操纵符,用于输出一个换行符并刷新缓冲区。除此之外,C++ 语言还提供了另外两个类似的操纵符。第一个经常使用的 flush ,用于刷新流,但不在输出中添加任何字符。第二个则是比较少用的 ends ,这个操纵符在缓冲区中插入空字符 null,然后后刷新它:
如果需要刷新所有输出,最好使用 unitbuf 操纵符。这个操纵符在每次执行完写操作后都刷新流:
cout << unitbuf << "first" << " second" << nounitbuf;
等价于:
cout << "first" << flush << " second" << flush;
nounitbuf 操纵符将流恢复为使用正常的、由系统管理的缓冲区刷新方式。
警告:如果程序崩溃了,则不会刷新缓冲区
如果需要使用最后的输出给程序错误定位,则必须确定所有要输出的都已经输出。为了确保用户看到程序实际上处理的所有输出, 最好的方法是保证所有的输出操作都显式地调用了 flush 或 endl 。
将输入和输出绑在一起
tie 函数可用 istream 或 ostream 对象调用,使用一个指向 ostream 对象的指针形参。调用 tie 函数时,将实参流绑在调用该函数的对象上。如果一个流调用 tie 函数将其本身绑在传递给 tie 的 ostream 实参对象上,则该流上的任何 IO 操作都会刷新实参所关联的缓冲区。
一个 ostream 对象每次只能与一个 istream 对象绑在一起 。如果在调用 tie 函数时传递实参 0,则打破该流上已存在的捆绑。
8.4. 文件的输入和输出
fstream 类型除了继承下来的行为外,还定义了两个自己的新操作—— open 和 close ,以及形参为要打开的文件名的构造函数。fstream 、ifstream 或 ofstream 对象可调用这些操作,而其他的 IO 类型则不能调用 。
警告:C++ 中的文件名
由于历史原因,IO 标准库使用 C 风格字符串(第 4.3 节 )而不是 C++ strings 类型的字符串作为文件名。在创建 fstream 对象时,如果调用 open 或使用文件名作初始化式,需要传递的实参应为 C 风格字符串,而不是标准库 strings 对象。程序常常从标准输入获得文件名。通常,比较好的方法是将文件名读入 string 对象,而不是 C 风格字符数组。假设要使用的文件名保存在 string 对象中,则可调用 c_str 成员(第 4.3.2 节 )获取 C 风格字符串。
检查文件打开是否成功
打开文件后,通常要检验打开是否成功,这是一个好习惯:
// check that the open succeeded if (!infile) { cerr << "error: unable to open input file: " << ifile << endl; return -1; }
将文件流与新文件重新捆绑
先关再开 ,如:
ifstream infile("in"); // opens file named "in" for reading infile.close(); // closes "in" infile.open("next"); // opens file named "next" for reading
清除文件流的状态
如果希望避免在每次 while 循环过程中创建新流对象,可将 input 的定义移到 while 之前。这点小小的改动意味着必须更仔细地管理流的状态。如果遇到文件结束符或其他错误,将设置流的内部状态,以便之后不允许再对该流做读写操作。关闭流并不能改变流对象的内部状态。如果最后的读写操作失败了,对象的状态将保持为错误模式,直到执行 clear 操作重新恢复流的状态为止。调用 clear 后,就像重新创建了该对象一样。
ifstream input; vector<string>::const_iterator it = files.begin(); // for each file in the vector while (it != files.end()) { input.open(it->c_str()); // open the file // if the file is ok, read and "process" the input if (!input) break; // error: bail out! while(input >> s) // do the work on this file process(s); input.close(); // close file when we're done with it input.clear(); // reset state to ok ++it; // increment iterator to get next file }
如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用 clear 清除该流的状态。
8.4.2. 文件模式
in | 打开文件做读操作 |
out | 打开文件做写操作 |
app | 在每次写之前找到文件尾 |
ate | 打开文件后立即将文件定位在文件尾 |
trunc | 打开文件时清空已存在的文件流 |
binary | 以二进制模式进行 IO 操作 |
从效果来看,为 ofstream 对象指定 out 模式等效于同时指定了 out 和 trunc 模式。 |
对同一个文件作输入和输出运算
fstream 对象既可以读也可以写它所关联的文件。fstream 如何使用它的文件取决于打开文件时指定的模式 。
默认情况下,fstream 对象以 in 和 out 模式同时打开。当文件同时以 in 和 out 打开时不清空。如果打开 fstream 所关联的文件时,只使用 out 模式,而不指定 in 模式,则文件会清空已存在的数据。如果打开文件时指定了 trunc 模式,则无论是否同时指定了 in 模式,文件同样会被清空。 下面的定义将 copyOut 文件同时以输入和输出的模式打开:
// open for input and output
fstream inOut("copyOut", fstream::in | fstream::out);
模式是文件的属性而不是流的属性
8.5. 字符串流
-
istringstream ,由 istream 派生而来,提供读 string 的功能。
-
ostringstream ,由 ostream 派生而来,提供写 string 的功能。
-
stringstream ,由 iostream 派生而来,提供读写 string 的功能。
表 8.5. stringstream 特定的操作
stringstream strm; |
创建自由的 stringstream 对象 |
stringstream strm(s); |
创建存储 s 的副本的 stringstream 对象,其中 s 是 string 类型的对象 |
strm.str() |
返回 strm 中存储的 string 类型对象 |
strm.str(s) |
将 string 类型的 s 复制给 strm ,返回 void |