目录
IO 类
● C++
语言不直接处理输入输出,而是通过一组定义在标准库中的类型来处理IO。 这些类型支持从设备读取数据、向设备写入数据的IO操作, 设备可以是文件、控制台窗口等。 还有一些类型允许内存IO,即, 从string
读取数据, 向string
写入数据。
● 我们已经使用过的 IO 类型和对象都是操纵 char 数据的。 iostream 定义了用于读写流的基本类型, fstream 定义了读写命名文件的类型, sstream 定义了读写内存 string 对象的类型。
为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵 wchar_t 类型的数据,宽字符版本的类型和函数的名字以一个 w 开始。wcin, wcout和wcerr是分别对应cin. cout和cerr的宽字符版对象。
注意: 宽字符版本的类型和对象与其对应的普通的char 版本的类型定义在同一个头文件中。 例如: 头文件fstream 定义了ifstream和wifstream 类型。
IO 对象无拷贝和赋值
注意: 不能拷贝和对IO对象赋值,也不能将形参和返回类型设置为流类型。 进行IO操作的函数通常以引用方式传递和返回流。 传递和返回的引用不能是const
的,因为会改变IO对象的状态。
ofstream out1, out2;
out1 = out2; //错误,不能对流对象赋值
ofstream print(ofstream); //不能初始化ofstream 参数
out2 = print(out2); //不能拷贝流对象
条件状态
IO 操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的, 而其他错误则 发生在系统深处,已经超出了应用程序可以修正的范围。下图列出了 IO 类所定义的一些函数和标志,可以帮助我们访问和操纵流的条件状态:
● 一个流一旦发生错误,其上后续的 IO 操作都会失败。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。
确定一个流对象的状态最简单的方法是将它当作一个条件来使用:
while(cin>>word)
while 循环检查”>>” 表达式返回的流的状态。 如果输入操作成功,流保持有效状态,则条件为真。
查询流的状态
● IO 库定义了一个与机器无关的 iostate 类型,它提供了表达流状态的完整功能。badbit 表示系统级错误,如不可恢复的读写错误。 在发生可恢复错误后,failbit 被置位,如期望读取数值却读出一个字符等错误。
如果 badit、failbit 和 eofbit 任一个被置位,则检测流状态的条件会失败。*使用good和fail是确定流的总体状态的正确方法。* 实际上,我们将流当作条件使用的代码就等价于 !fail( )。而 eof 和 bad 操作只能表示特定的错误。
管理条件状态
● 流对象的 rdstate 成员返回一个 iostate 值,对应流的当前状态。setstate 操作将给定条件位置位,表示发生了对应错误。
clear 不接受参数的版本清除(复位)所以错误标志位。执行 clear( ) 后,调用 good 会返回 true。带参数的 clear 版本接受一个 iostate 值,表示流的新状态。
auto old_state = cin.rdstate(); //记住cin 的当前状态
cin.clear(); //是cin有效
process_input(cin); //一个函数使用cin
cin.setstate(old_state); //将cin置为原有状态
管理缓冲区对象
os<<"please enter a value: "; //文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后在打印
● 每个输出流都管理一个缓冲区,用来保存程序读写的数据。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
● endl 完成换行并刷新缓冲区的工作;flush 刷新缓冲区,但不输出任何额外的字符;ends 向缓冲区插入一个空字符,然后刷新缓冲区。
● 如果想在每次输出操作后都刷新缓冲区, 我们可以使用unitbuf 操纵符。它告诉流在接下来的每次写操作之后都进行一次flush操作。 而nounitbuf 操纵符则重置流, 使其恢复使用正确的系统管理的缓冲区刷新机制:
cout<<unitbuf; // 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout<<nounitbuf; //回到正常的缓冲方式
关联输入输出流
● 当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起:
cin>>ival; // 导致cout的缓冲区被刷新
● 我们既可以将一个istream
对象关联到另一个ostream
,也可以将一个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 间的正常关联
在这段代码中, 为了将一个给定的流关联到一个新的输出流, 我们将流的指针传递给了tie。为了彻底解开流的关联, 我们传递了一个空指针。 每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream 。
文件输入输出
● 头文件 fstream 定义了三个类型来支持文件 IO : ifstream 从一个给定文件读取数据,ofstream 向一个给定文件写入数据,以及 fstream 可以读写给定文件。
● 这些类型提供的操作与我们之前已经使用过的对象cin 和cout 的操作一样。 我们还可以用IO 运算符(<< 和 >>)来读写文件, 可以用getline 从一个ifstream 读取数据。
使用文件流对象
● 当我们想要读写一个文件时,可以定义一个文件流对象, 并将对象与文件关联起来。
每个文件流类都定义了一个名为open 的成员函数, 它完成一些系统相关的操作, 来定位给定的文件, 并视情况打开为读或写模式。
在创建文件流对象时, 我们可以提供文件名(可选的)。 如果提供了一个文件名, 则open 会自动被调用:
ifstream in(ifile); // 构造一个ifstream 并打开给定文件
ofstream out; // 输出文件流未关联到任何文件
● 代码定义了一个输入流in,它被初始化为从文件读取数据,文件名由string 类型的参数ifile指定。 第二条语句定义了一个输出流out ,未与任何文件关联。
C++11 标准中,文件名既可以是库类型 string 对象,也可以是 C 风格字符数组,而旧版本的标准库只允许 C 风格字符数组。
成员函数open 和 close
● 如果我们定义了一个空文件流对象, 可以随后调用****open来将它与文件关联起来:
ifstream in(ifile); //构筑一个ifstream并打开给定文件
ofstream out; //输出文件流未与任何文件相关联
out.open(ifile + ".copy"); // 打开指定文件
如果调用open失败, failbit 会被置位, 因为调用open可能失败, 进行open是否成功的检测通常是一个好习惯:
if(out) //检查open 是否成功
// open 成功,我们可以使用文件了
如果open失败, 条件会为假,我们就不能使用out了。
● 一旦一个文件流已经打开, 它就保持与对应文件的关联。注意: 对一个已经打开的文件流调用 open 会失败, 并会导致 failbit 被置位。 随后的试图使用文件流的操作都会失败。 为了将文件流关联到另外一个文件, 必须首先关闭已经关联的文件。 一单文件成功关闭, 我们就可以打开新的文件:
in.close(); //关闭文件
in.open(ifile + “2”); // 打开另一个文件
如果open 成功,则open 会设置流的状态,使得good() 为 true 。
string 流
● istringstream 从 string 读取数据,ostringstream 向 string 写入数据,而头文件 stringstream 既可从 string 读数据也可向 string 写数据。
● 当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用 istringstream 。
● 当我们逐步构造输出,希望最后一起打印时,ostringstream 是很有用的。
● C++ 使用标准库类来处理面向流的输入与输出:
iostream 处理控制台 IO
fstream 处理命名文件 IO
stringstream 完成内存 string 的 IO
每个 IO 对象都维护一组条件状态,用来指出此对象上是否可以进行 IO 操作。