第8章 IO库
IO类
IO库类型和头文件
头文件:类
iostream(控制台的IO):istream ostream iostream
fstream(命名文件的IO):ifstream ofstream fstream
sstream(内存string对象的IO):istringstream ostringstream stringstream
PS:
ifstream、istringstream继承自istream;
ofstream、ostringstream继承自ostream;
通常可以将一个派生类对象当作其基类对象来使用,例如可以对ifstream对象使用IO运算符(>>和<<)或getline等。
IO对象无拷贝和赋值
由于不能拷贝IO对象,因此不能将形参或返回类型设置为流类型;
进行IO操作的函数通常以引用的方式传递和返回流;
读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
条件状态
IO类定义了一些函数和标志,以访问和操纵流的条件状态(condition state)。
以下strm表示某种IO类型(istream等),s是某个流对象
strm::iostate iostate是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit 用来指出流已崩溃
strm::failbit 用来指出一个IO操作失败了
strm::eofbit 用来指出流达到了文件结束
strm::goodbit 用来指出流未处于错误状态(此值保证为0)
s.eof() 若流s的eofbit置位,则返回true
s.bad() 若流s的badbit置位,则返回true
s.good() 若流s处于有效状态,则返回true
s.clear() 将流s中所有条件状态位复位,将流的状态设置为有效(goodbit也复位了);返回void
s.clear(flags) 根据给定的flags标志位,将流s中对应条件状态位复位;flags的类型为strm::iostate;返回void
s.setstate(flags) 根据给定的flags标志位,将流s中对应条件状态位置位;flags的类型为strm::iostate;返回void
s.rdstate() 返回流s的当前条件状态,返回值类型为strm::iostate
流的条件状态位
badbit表示系统级错误,如不可恢复的读写错误。一旦badbit被置位流就无法再使用了;
当badbit被置位时,或者在发生可恢复错误后,failbit会被置位,如期望读取数值却读到一个字符等错误。这种错误可以修正,流还可以继续使用;
如果达到文件结束位置,eofbit和failbit都被置位;
当流未发生错误,goodbit保持为0;
如果badbit、failbit、eofbit任一被置位,则检测流状态的条件会失败;
例如 while(cin>>val) 在读到文件结束时终止循环。
流的条件状态查询函数
所有错误位均未置位时,good返回true(注意:此时goodbit为0而不是1);
bad、fail、eof在特定的对应错误位被置位时返回true(注意:badbit或eofbit被置位时,failbit也会被置位,所以fail也会返回true);
可见:good或fail是确定流的总体状态的方法。
例如
实际上 while(cin>>val)
就等价于 while(cin>>val && cin.good())
或 while(cin>>val && !cin.fail())
管理输出缓冲
每个输出流都管理一个缓冲区;
由于设备的写操作可能很耗时,允许操作系统将多个输出操作暂存在缓冲区,然后组合为单一的设备写操作,可以带来很大的性能提升。
缓冲区刷新即:数据真正写到输出设备或文件
导致缓冲区刷新的原因:
程序正常结束;(实际上,缓冲刷新是main函数return操作的一部分;程序异常终止时输出缓冲区不会被刷新)
缓冲区满;
使用操纵符endl显式刷新缓冲区;
写到cerr;(在每个输出操作之后,可以用操纵符unitbuf设置流的内部状态,来清空缓冲区;而默认情况下,对流对象cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的)
一个输出流被关联到了另一个流的情况;(例如,默认情况下,cin和cerr都关联到cout,因此,读cin或写cerr都会导致cout的缓冲区被刷新)
刷新输出缓冲区
以下操纵符需出现在某个输出操作之后。(cout、cerr等)
endl:换行并刷新缓冲区
flush:仅刷新缓冲区
ends:向缓冲区插入一个空格符,再刷新缓冲区
unitbuf操纵符
如果想在每次输出操作后系统都自动刷新缓冲区,可以使用unitbuf操纵符;
unitbuf告诉流在接下来的每次写操作之后都自动进行一次flush操作。
cout << unitbuf;
cout << nounitbuf; //回到正常的缓冲方式
关联输入和输出流
当一个输入流被关联到一个输出流时,使用该输入流的操作时会先刷新关联的输出流。
标准库将cin与cout关联在一起。
tie函数
tie函数有两个重载的版本:
一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回指向该输出流的指针;如果本对象未关联到流,则返回空指针。
另一个版本接受一个指向ostream的指针,将自己关联到此ostream。
例如:
ostream* old_tie = cin.tie(); //old_tie指向当前关联到cin的流(系统默认是cout)
cin.tie(&cerr); //将cin与cerr关联
cin.tie(nullptr); //解除cin与任何流的关联
cin.tie(old_tie); //重建cin与cout的关联
PS:
可以将一个istream关联到一个ostream,也可以将一个ostream关联到另一个ostream;
每个流最多关联到一个流,多个流可以同时关联到同一个ostream。
文件输入输出
头文件fstream定义了三个类型来支持文件IO:
ifstream:读文件
ofstream:写文件
fstream:读写文件(将在第17章讨论)
除了继承自iostream类型的>>、<<、getline等行为之外,fstream中定义的类型有其特有的管理与流关联的文件的成员。
fstream特有的操作:
fstream fs 创建一个未绑定的文件流;fstream是头文件fstream中定义的一种类型
fstream fs(s) 创建一个fstream,并打开(此时open会自动被调用)名为s的文件;s可以是string类型或者指向C风格字符串的指针(C++11之前只允许C风格的字符数组);这些构造函数数都是explicit(第7章)的;默认的文件模式mode依赖于fstream的类型
fstream fs(s, mode) 与前一个构造函数类似,但按指定mode打开文件
fs.open(s) 打开名为s的文件,并将文件与fs绑定;默认的文件mode依赖于fstream的类型;返回void
fs.close() 关闭与fs绑定的文件;返回void
fs.is_open() 返回bool值;指出与fs关联的文件是否成功打开尚且未关闭
用fstream代替iostream&
在要求使用基类对象的地方,我们可以用继承类型的对象来替代;
这意味着,接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用;
例如,如果有一个函数接受一个ostream&参数,调用这个函数时,可以传给它一个ofstream对象;(isstream&和ifstream也类似)
成员函数open和close
如果调用open失败,failbit会被置位;
fs.open(s);
if(fs) //如果open失败,则条件为假
对一个已经打开的文件流调用open会失败,并导致failbit被置位,随后的文件流操作也都会失败;为了将文件流关联到另一个文件,必须首先关闭已关联的文件。
fs.close();
若open成功,则open会设置流的状态,使得good()为true。
当一个fstream对象被销毁时,close会自动被调用。
文件模式(file mode)
in 读
out 写
app 每次写操作前均定位到文件尾
ate 打开文件后立即定位到文件尾
trunc 截断文件
binary 以二进制方式进行IO
文件模式规则:
(1)只可对ofstream或fstream对象设定out模式;
(2)只可对ifstream或fstream对象设定in模式;
(3)只有当out被设定时才可设定trunc模式;
(4)只要trunc未被设定,就可以设定app模式;在app模式下,即使没有显式设定out,文件也总是以输出方式被打开;
(5)默认情况下,即使未设定trunc,以out模式打开的文件也会被截断;为了保留以out模式打开的文件内容,必须同时指定app模式,这样就会将数据追加到文件尾;或者同时指定in模式,同时进行读写操作(第17章);
(6)ate和binary模式可用于任何类型的文件流对象,且可与其它任何文件模式组合使用;
当未显式指定文件模式时,各文件流类型的默认模式:
ifstream:in
ofstream:out
fstream:in和out
一些例子:
ofstream out(s1); //默认为out模式,文件被截断
ofstream out2(s1, ofstream::out); //显式指定out,会隐式地截断文件(参考“文件模式规则”:第5条)
ofstream out3(s1, ofstream::out | ofstream::trunc); //显式截断文件(只有out也被设定,trunc才能被设定;参考“文件模式规则”:第3条)
ofstream app(s2,ofstream::app); //隐式地设定了out
ofstream app2(s2,ofstream::out | ofstream::app); //与上一条等价
PS:以上是在初始化流并隐式打开文件时指定文件模式,此外在使用open打开文件时也同样可以指定文件模式。
string流
sstream头文件定义了三个类型来支持内存IO:
istringstream:从string读取数据
ostringstream:向string写入数据
stringstream:可从string读取数据,或向string写入数据
除了继承自iostream头文件的>>、<<、getline等操作,sstream头文件定义的类有其特有操作:
sstream st st是一个未绑定的stringstream对象;sstream是头文件sstream中定义的某个类型
sstream st(s) st是一个sstream对象,保存string s的一个拷贝;此构造函数时explicit(第7章)的
st.str() 返回st所保存的string的拷贝
st.str(s) 将string s拷贝到st中;返回void
使用istringstream
例子:
string line, word;
while(getline(cin, line)) //读取一行
{
istringstream iss(line); //将string流iss绑定到读入的一行line(流对象iss中存有line的一个拷贝)
while(iss>>word) //使用输入运算符从iss流中依次读取各个字符串
cout<<word<<endl;
}
问题:若将istringstream对象定义在循坏之外程序还能完成之前的功能吗?
string line, word;
istringstream iss;
while(getline(cin, line))
{
iss.str(line); //调用str函数将流iss绑定到line(流对象iss中存有line的一个拷贝)
while(iss>>word)
cout<<word<<endl;
//iss.clear();
}
不能。程序只会成功读取并处理文件中的第一行就终止了。
原因:当string流中的数据全部读出后,同样会触发“文件结束”信号,此时流iss不在有效了。
改正:可在某次使用完流之后调用clear函数使所有条件状态位复位,使其重新恢复有效状态。
使用ostringstream
例子:
ostringstream oss;
string word;
while(cin>>word)
oss << word << " ";
cout<< oss.str() <<endl;
以上程序完成的事情是:依次读取所有输入的单词,并将单词“写入”到oss流对象,最后使用str函数将oss流对象包含的string以一行打印出来。(PS:最后也有一个空格)