-
#1.IO类
- IO对象无拷贝或赋值
- 条件状态
- 管理输出缓冲
-
#2.文件输入输出
- 使用文件流对象
- 文件模式
-
#3.string流
- 使用istringstream
- 使用ostringstream
C++语言不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。
#1. IO类
iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。
1.1 IO对象无拷贝或赋值
ofstream out1,out2;
out1 = out2; //错误:不能对流对象赋值
ofstream print(ofstream); //错误:不能初始化ofstream参数
out2 = print(out2); //错误:不能拷贝流对象
由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型。进行IO的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
1.2 条件状态
IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。表中列出了IO类定义了一些函数和标志,可以帮助我们访问和操作流的条件状态。
函数 | 状态 |
---|---|
strm::iostate | strm是一种IO类型。iostate 是一种机器相关类型,提供了表达条件状态的完整功能 |
strm::badbit | strm::badbit 用来指出流已崩溃 |
strm::failbit | strm::failbit 用来指出一个IO操作失败了 |
strm::eofbit | strm::eofbit 用来指出流到达了文件结束 |
strm::goodbit | strm::goodbit 用来指出流未处于错误状态。此值保证为零。 |
查询流的状态
将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。有时我们需要知道流为什么失败。
IO库提供了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。IO库定义了4个iostate类型的constexpr值表示特定的位模式。badbit表示系统级错误,如不可恢复的读写错误。在发生可恢复错误后,failbit被置位。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果bidbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。
管理条件状态
流对象的rdstate成员返回一个iostate值,对应流的当前状态。setState操作将给定条件位置位,表示发生了对应错误。
auto old_state = cin.rdstate(); //记住cin的当前状态
cin.clear(); //使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //将cin置为原有状态
1.3 管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如,如果执行下面的代码:
os << "Please enter a value";
文本可能立即打印出来,也可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。
导致缓冲刷新的原因有很多:
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
- 缓冲区满时,需要刷新缓冲区,而后新的数据才能写入缓冲区。
- 我们可以使用操纵符如endl来显示刷新缓冲区。
- 在每个输出操作之后,我们可以使用操纵符unitbuf设置流的内部状态,来清空缓冲区。
- 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。
刷新输出缓冲区
我们已经使用过操纵符endl,它完成换行并刷新缓冲区的工作。
cout << "hi!" << endl; //输出hi和一个换行,然后刷新缓冲区
cout << "hi!" << flush; //输出hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" << ends; //输出hi和一个空字符,然后刷新缓冲区
unitbuf操作符
如果想在每次操作后都刷新缓冲区,可以使用unitbuf操纵符。它告诉流接下来的每次写操作之后都进行一次flush操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
cout << unitbuf; //所有输出操作后都会立即刷新缓冲区
//任何输出都立即刷新,无缓冲
cout << nounitbuf; //回到正常的缓冲方式
==如果程序崩溃,输出缓冲区不会被刷新。==
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起,因此下面语句:
cin >> ival;
导致cout缓冲区被刷新。
==交互式系统应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。==
#2 文件输入输出
头文件fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。
除了继承自iostream类型的行为之外,fstream中定义的类型还增加了一些新的成员管理与流关联的文件。表中列出了这些操作,我们可以对fstream、ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。
fstream特有的操作 | 含义 |
---|---|
fstream fstrm; | 创建一个未绑定的文件流。fstream是头文件fstream中定义的一个类型。 |
fstream fstrm(s); | 创建一个fstream,并打开名为s的文件。s可以是string类型,或者是一个指向C风格字符串的指针。这些构造函数都是explict的。默认的文件模式依赖于fstream的类型 |
fstream fstrm(s,mode); | 与前一个构造函数类似,但按指定mode打开文件。 |
fstrm.open(s) | 打开名为s的文件,并将文件与fstrm绑定。返回void。 |
fstrm.close() | 关闭与fstrm绑定的文件。返回void。 |
fstrm.is_open() | 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭。 |
2.1 使用文件流对象
当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。每个文件流类都定义了一个名为open的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。
ifstream in(ifile); //构造一个ifstream并打开给定文件
ofstream out; //输出文件流并未关联任何文件
用fstream代替iostream&
接受一个iostream类型引用参数的函数,可以用一个对应的fstream类型来调用。
成员函数open和close
如果我们定义了一个空文件流对象,可以随后调用open来将它与文件关联起来:
ifstream in(ifile); //构筑一个ifstream并打开给定文件
ofstream out; //输出文件流并未与任何文件关联
out.open(ifile + ".copy"); //打开指定文件
如果调用open失败,failbit会被置位。
自动构造和析构
for(auto p = argv + 1;p != argv + argc;++p) {
ifstream input(*p); //创建输入流并打开文件
if(input) { //如果文件打开成功,“处理”此文件
process(input);
}else {
cerr << "couldn't open: " << string(*p);
}
} //每个循环步input都会离开作用域,因此会被销毁。
==当一个fstream对象被销毁时,close会自动被调用。==
2.2 文件模式
每个流都有一个关联的文件模式,用来指出如何使用文件。表给出了文件模式和它们的定义:
文件模式 | 含义 |
---|---|
in | 以读方式打开 |
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式IO |
无论用哪种方式打开文件,我们都可以指定文件模式,指定文件模式有如下限制:
- 只可以对ofstream或fstream对象设定out模式。
- 只可以对ifstream或fstream对象设定in模式。
- 只有当out也被设定时才可设定trunc模式。
- 只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显示指定out模式,文件也总是以输出方式被打开。
- 默认情况下,即使没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
- ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用
每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开。
以out模式打开文件会丢失已有数据
默认情况下,我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式:
//在这几条语句中,file1都被截断
ofstream out("file1"); //隐含以输出模式打开文件并截断文件
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);
==保留被ofstream打开文件中已有数据的唯一方法是显示指定app或in模式。==
每次调用open时都会确定文件模式
对于一个给定流,每当打开文件时,都可以该变其文件模式:
ofstream out; //未指定文件打开模式
out.open("scratchpad"); //模式隐含设置为输出和截断
out.close(); //关闭out,以便我们将其用于其他文件
out.open("precious",ofstream::app); //模式为输出和追加
out.close();
第一个open调用没指定输出模式,文件隐式地以out模式打开。通常情况下,out模式意味着同时使用trunc模式。因此,当前目录名为scratchpad的文件的内容将被清空。当打开名为precious的文件时,我们指定了append模式。文件中已有的数据都得以保留,所有写操作都在文件末尾进行。
==每次打开文件时,都要设置文件模式,可能是显示地设置,也可能是隐式地设置。当程序未指定模式时,就使用默认值。==
#3. string流
istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream既可从string读取数据也可向string写数据。
stringstream特有的操作 | 含义 |
---|---|
sstream strm; | strm是一个未绑定的stringstream对象。 |
strm.str() | 返回strm所保存的string的拷贝 |
strm.str(s) | 将string s拷贝到strm中。返回void |
3.1 使用istringstream
struct PersonInfo {
string name;
vector<string> phones;
};
string line, word; //分别保存来自输入的一行和单词
vector<PersonInfo> people; //保存来自输入的所有记录
//逐行从输入读取数据,直至cin遇到文件尾
while (getline(cin,line)) {
PersonInfo info; //创建一个保存此记录的数据对象
istringstream record(line); //将记录绑定到刚读入的行
record >> info.name; //读取名字
while (record >> word) { //读取电话号码
info.phones.push_back(word); //保存它们
}
people.push_back(info); //将此记录追加到people末尾
}
3.2 使用ostringstream
但我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。
for(const auto &entry:people) { //对于people中的每一项
ostringstream formatted,badNums; //每个循环步创建的对象
for(const auto &nums:entry.phones) {
if(!valid(nums)) {
badNums << " " << nums; //将数的字符串形式存入badNums
}else {
formatted << " " << format(nums);
}
}
if(badNums.str().empty()) { //没有错误的数
os << entry.name << " "
<<formatted.str() << endl;
}else {
cerr << "input error: " << entry.name
<<" invalid numbers " << badNums.str() << endl;
}
}