6.3 标准库类型:IO库
简介
iostream定义了读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。
Tips:为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵
wchar_t
类型的数据,例如wcin、wcout和wcerr分别对应cin、cout和cerr的宽字符版对象。
头文件 | 类型 |
---|---|
iostream | istream和wistream:从流读取数据 ostream和wostream:向流写入数据 iostream和wiostream:读写流 |
fstream | ifstream和wifstream:从文件读取数据 ofstream和wofstream:向文件写入数据 fstream和wfstream:读写文件 |
sstream | istringstream和wistringstream:从string读取数据 ostringstream和wostringstream:向string写入数据 stringstream和wstringstream:读写string |
IO类
1. 不同IO类型的联系
类型ifstream和istringstream都继承自istream,即我们可以像使用istream对象一样来使用ifstream和istringstream。也就是说我们是如何使用cin的,就可以同样地使用ifstream和istringstream类型的对象。例如,可以对一下ifstream或istringstream对象调用getline,也可以使用>>
从一个ifstream或者istringstream对象中读取数据。
2. IO对象无拷贝或者赋值
我们不能拷贝或者对IO对象赋值,因此也不能将形参或者返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流,读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
ofstream out1, out2;
out1 = out2; // 错误: 不能对流对象赋值
ofstream print(ofstream); // 错误: 不能初始化ofstream参数
out2 = print(out2); // 错误: 不能拷贝流对象
3. IO类的条件状态
IO操作一个与生俱来的问题就是可能发生错误,下面列举出了IO类所定义的一些函数和标志,用于帮助我们访问和操纵流的条件状态:
条件状态 | 含义 |
---|---|
strm::badbit | 流已崩溃 |
strm::failbit | IO操作失败 |
strm::eofbit | 流到达了文件结束 |
strm::goodbit | 流未处于错误状态,此值保证为0 |
s.eof() | 若s的eofbit置位,则返回true |
s.bad() | 若s的failbit或者badbit置位,则返回true |
s.good() | 若s处于有效状态,则返回true |
s.clear() | 将流s中所有条件状态复位,将流状态设置为有效,返回void |
s.clear(flags) | 根据给定的flags标志位,将流s中对应条件状态位复位,返回void |
s.setstate(flags) | 根据给定的flags标志位,将流s中对应条件状态位置位,返回void |
s.rdstate() | 返回流s的当前条件状态 |
举个IO错误的例子:
// 如果我们在标准输入上键入Boo, cin就会进入错误状态
// 如果我们输入一个文件结束标识, cin也会进入错误状态
int ival;
cin >> ival;
Tips:一旦一个流发生错误,其上后续的IO操作都会失败。只有当一个流处于无措状态时,我们才可以从它读取数据或者向它写入数据。由于流可能处于错误状态,因此代码通常在使用一个流之前检查它是否处于良好状态:
// 确定一个流对象状态的最简单方法是将它作为一个条件来使用 while (cin >> word) // ok: 读操作成功
4. 管理输出缓冲
Tips:有了缓冲机制,操作系统就可以将程序的多个输出组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合成单一的设备写操作可以带来很大的性能提升。
举个例子,当我们执行如下代码时,字符串可能会立即打印出来,也可能被操作系统保存在缓冲区中,随后再打印。
cout << "tomocat";
导致缓冲刷新(即数据真正写到输出设备或文件)的原因有很多:
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
- 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区
- 使用操作符endl来显式刷新缓冲区
- 在每个输出操作之后,我们可以使用操纵符unitbuf设置流的内部状态来清空缓冲区(默认清空下对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的)
- 一个输出流可能被关联到另一个流,在这种情况下当读写被关联的流时,关联到的流的缓冲区会被刷新(默认情况下cin和cerr都关联到cout,因此读cin或写cerr都会导致cout的缓冲区被刷新)
cout << "cat" << endl; // 输出cat和一个换行符, 然后刷新缓冲区
cout << "cat" << flush; // 输出cat, 然后刷新缓冲区
cout << "cat" << ends; // 输出cat和一个空字符, 然后刷新缓冲区
cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区
cout << nounitbuf; // 恢复到正常的缓冲方式
Tips:如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
文件输入输出
1. 操作
头文件fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。除了继承自iostream类型的行为之外,fstream中定义的类型还增加了一些新的成员来管理与流相关联的文件。下面列举了这些操作,我们可以对fstream、ifstream和ofstream对象调用这些操作:
操作 | 含义 |
---|---|
fstream fstrm | 创建一个未绑定的文件流 |
fstream fstrm(s) | 创建一个fstream,并打开名为s的文件,默认文件模式mode依赖于fstream的类型 |
fstream fstrm(s, mode) | 与前一个构造函数类似,但按指定mode打开文件 |
fstrm.open(s) | 打开名为s的文件,并将文件与fstrm绑定,默认的文件mode依赖于fstrm的类型,返回void |
fstrm.close() | 关闭与fstrm绑定的文件,返回void |
fstrm.is_open() | 返回一个bool值,指出与fstrm关联的问价是否成功打开且尚未关闭 |
2. 例子
Tips:每个文件流类都定义了一个名为open的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。创建文件流对象时,如果提供了一个文件名,那么open会自动被调用。
ifstream in(ifile); // 构造一个ifstream并打开给定文件
ofstream out; // 输出文件流未关联到任何文件
out.open(ifile + ".copy"); // 打开指定文件
// 如果调用open失败, failbit会被置位, 所以在使用open后需要检查open是否成功
if (out) { // 检查open是否成功
// do something
}
// 一旦一个文件流已经打开, 它就会保持与对应文件的关联, 对一个已经打开的文件流调用open会失败, 导致failbit被置位
// 为了将文件流关联到另一个文件, 必须首先关闭已经关联的文件
in.close(); // 关闭文件
in.open(ifile + "2"); // 打开另一个文件
Tips:当一个fstream对象被销毁时,close会自动被调用。
// 对每个传递给程序的文件执行循环操作
for (auto p = argv + 1; p != argv + argc; ++p) {
ifstream input(*p);
if (input) {
process(input);
} else {
cerr << "couldn't open: " + string(*p);
}
} // 每个循环步input都会离开作用域, 因此会被自动销毁, 与之关联的文件会被自动关闭
3. 文件模式
每个流都有一个关联的文件模式,用来指出如何使用文件:
文件模式 | 含义 |
---|---|
in | 以读方式打开 |
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
注意:
Tips:保留被ofstream打开的文件中已有数据的唯一方式是显式指定app或in模式。
- 只可以对ofstream或fstream对象设定out模式
- 只可以对ifstream和fstream对象设定in模式
- 只有当out也被设定时才可以设定trunc模式
- 只要trunc没被设定就可以设定app模式,app模式下即使没有显式指定out模式文件也总是以输出方式被打开
- 默认情况下即使没有指定trunc,以out模式打开的文件也会被截断,为了保留以out模式打开的文件内容:
- 同时指定app模式,这样数据只会追加写到文件末尾
- 同时指定in模式,即打开文件同时进行读写操作
- ate和binary模式可用于任何类型的文件流对象,且可以与任何其他文件模式组合使用
Tips:每个文件流类型都定义了一个默认的文件模式。与ifstream关联的文件默认以in模式打开,与ofstream关联的文件默认以out模式打开,与fstream关联的文件默认以in和out模式打开。
ofstream out; // 未指定文件打开模式
out.open("a.txt"); // 模式隐式设置为out和trunc
out.close(); // 关闭out, 以便我们可以打开其他文件
out.open("b.txt", ofstream::app); // 模式显式设置为out和app
out.close();
string流
1. 操作
sstream头文件定义了三个类型来支持内存IO,将string当做一个流来处理:
- istringstream:从string读取数据
- ostringstream:向string写入数据
- stringstream:既可从string读取数据也可以向string写入数据
上述三种类型都分别继承自我们前面提到的iostream头文件中定义的类型,除了继承得到的操作,sstream中定义的类型还增加了一些成员来管理与流相关联的string。下面列举出来的操作可以对stringstream对象调用,但是不能对其他IO类型调用:
操作 | 含义 |
---|---|
sstream strm | strm是一个未绑定的stringstream对象 |
sstream strm(s) | strm是一个sstream对象,保存strng s的一个拷贝 |
strm.str() | 返回strm保存的string的拷贝 |
strm.str(s) | 将string s拷贝到strm中,返回void |
2. 使用istringstream
Tips:从string中读取数据可以使用ostringstream。
struct PersonInfo {
string name;
vector<string> phones;
};
/*
输入样例:
morgan 2015552368 8625550123
drew 9735550130
lee 6095550132 2015550175 8005550000
*/
string line, word;
vector<PersonInfo> people;
while (getline(cin, line)) {
PersonInfo info;
istringstream record(line); // 将istringstream绑定到刚输入的行
record >> info.name; // 从istringstream中读取数据
while (record >> word)
info.phones.push_back(word);
people.push_back(info);
}
3. 使用ostringstream
Tips:向string中写入内容可以用ostringstream。
for (const auto &entry : people) {
ostringstream formatted, badNums;
for (const auto &num : entry.phones) {
if (!valid(nums)) {
badNums << " " << nums;
} else {
forrmatted << " " << format(nums);
}
}
if (badNums.str().empty()) {
cout << entry.name << " "
<< formatted.str() << endl;
} else {
cerr << "input error: " << entry.name
<< " invalid numbers(s) " << badNums.str() << endl;
}
}