【第八章】 IO库
8.1 IO类
iostream定义了用于读写流的基本类型
fstream定义了读写命名文件的类型
sstream定义了读写内存string对象的类型
IO类型之间的关系:
类型ifstream和istringstream都继承自istream。因此我们可以像使用istream对象一样来使用ifstream和istringstream对象。
类型ofstream和ostringstream都继承自ostream。
8.1.1 IO对象无拷贝或赋值
不能拷贝或者对IO对象赋值:
ofstream out1, out2;
out1 = out2; // 错误,不能对流对象赋值
ofstream print(ofstream); // 错误,不能初始化ofstream参数
out2 = print(out2); // 错误,不能拷贝流对象
由于不能拷贝IO对象,因此也不能将形参或返回类型设置为流类型。
进行IO操作的函数通常以引用方式传递和返回流。
读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
8.1.2 条件状态
1、一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,才可以从它读取数据,向它写入数据。
2、由于流可能处于错误状态,因此通常在使用一个流之前检查它是否处于良好状态。
练习8.1 编写函数,接受一个istream&参数,返回值类型也是istream&。此函数从给定流中读取数据,直至遇到文件结束标识时停止。将读取的数据打印。完成后,在返回流之前,对流进行复位,使其处于有效状态。
【解析】
#include<iostream>
using namespace std;
istream & fun(istream &in)
{
int v;
// 直到遇到文件结束符才停止读取
while (in >> v, !in.eof())
{
if (in.bad())
{
throw runtime_error("IO流错误");
}
if (in.fail())
{
cerr << "数据错误,请重试!" << endl;
in.clear();
in.ignore(100, '\n');
continue;
}
cout << v << endl;
}
in.clear();
return in;
}
int main()
{
cout << "请输入一些整数,按ctrl+z结束" << endl;
fun(cin);
system("pause");
return 0;
}
3、while(cin >> i) 在以下情况下回终止
1)遇到文件结束符 2)遇到IO流错误 3)读入了无效数据
8.1.3 管理输出缓冲
导致缓冲刷新(即数据真正写到输出设备或文件)的原因:
1)程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
2)缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区
3)可以使用操纵符如endl来显式刷新缓冲区
4)在每个输出操作后,我们可以使用操纵符unitbuf设置流的内部状态,来清空缓冲区。
5)一个输出流可能被关联到另一个流。这种清空下,当读写被关联的流时,关联到的流的缓冲区会被刷新。
例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新
刷新输出缓冲区:
已经使用过endl操纵符来完成换行并刷新缓冲区的工作。IO库中海油两个类似的操纵符:flush和ends
其中,flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。
unitbuf操纵符:
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。
它告诉流在接下来的每一次写操作后都进行一次flush操作。
而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
cout<< unitbuf; // 所有输出操作后都会立即刷新缓冲区
cout<< nounitbuf; // 回到正常的缓冲方式
关联输入和输出流:
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cin和cout关联在一起。因此,下面的语句
cin>>i;
导致cout的缓冲区刷新
tie有两个重载的版本:
1)一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。
2)第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&O)将流x关联到输出流o
既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream:
cin.tie(&out); // 将cin和cout关联在一起。不过标准库已经将它们关联在一起
// old_tie指向当前关联到cin的流
ostream *old_tie = cin.tie(nullptr); // cin不再与其他流关联
// 将cin与cerr关联
cin.tie(&cerr);
每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream
8.2 文件输入输出
1、头文件fstream定义了三个类型来支持文件IO:
1)ifstream从一个给定文件读取数据
2)ofstream向一个给定文件写入数据
3)fstream可以读写给定文件
2、fstream特有的操作
1)fstream fstrm; // 创建一个未绑定的文件流。fstream是头文件fstream中定义的一个类型
2)fstream fstrm(s); // 创建一个fstream,并打开名为s的文件。s可以是string类型,也可以是一个指向C风格字符串的指针
3)fstrm.open(s); // 打开一个名为s的文件,并将文件与fstrm绑定。s可是一个string类型或者一个指向C风格字符串的指针
4)fstrm.close(); // 关闭与fstrm绑定的文件。返回void
5)fstrm.is_open(); // 返回一个bool值,指出与fstrm关联的文件是否打开成功且尚未关闭
8.2.1 使用文件流对象
当我们要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。
用fstream代替iostream&
在要求使用基类型对象的地方,可以用继承类型的对象来代替。
这意味着,接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用。
成员函数open和close
如果调用open失败,failbit会被置位。所以进行open是否成功的检测是一个好习惯
ofstream out;
out.open("12.txt");
if(out) // 检查open是否成功
一旦open失败,随后的试图使用文件流的操作都会失败。为了将文件流关联到另一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,则可以使用out对象继续打开新的文件
如果open成功,则open会设置流的状态,使得good()为true
自动构造和析构:
当一个fstream对象离开其作用域时,与之关联的文件会自动关闭。即当一个fstream对象被销毁时,close会被自动调用
练习8.4 以读模式打开一个文件,将其内容读入到一个string的vector中,将每行作为一个独立的元素存于vector中
【解析】
这里我采用了fstream类型,当然也可以使用ifstream类型(只读)
#include<vector>
#include<string>
#include<iostream>
using namespace std;
#include<fstream>
int main()
{
fstream input;
input.open("test.txt");
vector<string> words;
if (input)
{
string line;
while (getline(input, line))
{
words.push_back(line);
}
}
else
{
cout << "error";
}
input.close();
for (auto i : words)
{
cout << i << endl;
}
system("pause");
return 0;
}
练习8.5 重写上面的程序,将每个单词作为一个独立的元素进行存储
【解析】
将while循环的条件改为input >> line即可
8.2.2 文件模式
每个流都有一个关联的文件模式,用来指出如何使用文件。
1)in 以读方式打开
2)out 以写方式打开
3)app 每次写操作前均定位到文件末尾
4)ate 打开文件后立即定位到文件末尾
5)trunc 截断文件
6)binary 以二进制方式进行IO
无论用那种方式打开文件,都可以指定文件模式,调用open打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以。指定文件模式有如下限制:
1)只可以对ofstream或fstream对象设定out模式
2)只可以对ifstream或fstream对象设定in模式
3)只有当out也被设定时,才可以设定trunc模式
4)只要trunc没被设定,才可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开
5)默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。
为了保留以out模式打开的文件的内容,必须同时指定app模式,这样只会将数据追加写到文件末尾;
或者同时指定in模式,即打开文件同时进行读写操作。
6)ate和binary模式可以用于任何类型的文件流对象,且可以与其他任何文件模式组合使用
默认情况下,与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开
以out模式打开文件会丢弃已有数据:
默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。
阻止一个ofstream清空给定文件内容的方法是同时指定app模式:
即:ofstream app("file", ofstream::out | ofstream::app);
保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式
每次调用open时都会确定文件模式:
对于一个给定流,每当打开文件时,都可以改变其文件模式。
ofstream out; // 未指定文件打开模式
out.open("file"); // 模式隐含设置为输出和截断
out.close(); // 关闭out,以便我们将其用于其他文件
out.open("file1", ofstream::app); // 模式为输出和追加(append模式)
out.close();
8.3 string流
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样
1)istringstream从string读取数据
2)ostringstream向string写入数据
3)stringstream既可以从string读数据也可以向string写数据
stringstream特有的操作:
1)sstream strm; // strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义个一个类型
2)sstream strm(s); // strm是一个sstream对象,保存string s的一个拷贝。
3)strm.str(); // 返回strm所保存的string的拷贝
4)strm.str(s); // 将string s拷贝到strm中,返回void