IO类
前面提到的IO
类型和对象都是操纵char
数据的,并且都是关联到用户的控制台窗口的。我们还有其他IO
需求:
- 除了从控制台进行
IO
操作,应用程序还经常需要读写文件 - 除了操纵
char
数据还需要操纵string
为了支持上述操作,在istream
和ostream
,标准库还定义了一些其他IO
类型。分别定义在三个独立的头文件中:
-
iostream
:定义了用于读写流的基本类型 -
fstream
:定义了读写命名文件的类型 -
sstream
:定义了读写内存string
对象的类型
标准库通过继承机制
inheritance
是我们可以忽略不同类型的流之间的差异。比如ifstream
和istringstream
都继承自istream
,因此我们可以像使用istream
对象一样来使用ifstream
和istringstream
对象。我们是如何使用cin
的也可以同样地使用这些类型的对象。比如可以对一个ifstream
或istringstream
对象调用getline
,也可以使用>>
从一个ifstream
或istringstream
对象中读取数据。
1. IO对象无拷贝或者赋值
我们不能拷贝或对IO
对象赋值,因此我们也不能将形参或返回类型设置为流类型。进行IO
操作时通常是以引用方式传递和返回流。读写一个IO
对象会改变其状态,因此传递和返回的引用是非const
的。
2. 条件状态
IO
操作与生俱来的问题是可能发生错误,一些错误是可修复的,而其他错误则可能发生在系统深处超出了应用程序可以修正的范围。下面列出来IO
类所定义的一些函数和标记:
-
strm::badbit
:指出流已崩溃 -
strm::failbit
:支持一个IO
操作失败了 -
strm::eofbit
:指出流到达了文件结束 -
strm::goodbit
:指出流未处于崔武状态 -
s.eof()
:若流s
的eofbit
置位,则返回true
-
s.fail()
:若流s
的failbit
或badbit
置位,则返回true
-
s.bad()
:若流s
的badbit
置位,则返回true
-
s.good()
:若流s
处于有效状态,则返回true
-
s.clear()
:将流s
的所有条件状态复位,将流的状态设置为有效,返回void
-
s.clear(flags)
:将流s
的对应条件状态复位 -
s.setstate(flags)
:设置流s
的对应条件状态
一个流一旦发生错误,其后续的IO
操作都会失败,因此代码通常需要在使用一个流之前检查它是否处于良好状态,确定一个流对象的最简单方法是将它作为一个条件使用:
while (cin >> word)
// ok: 读操作成功...
将流作为条件使用,只能告诉我们流是否有效而无法告诉我们具体发生了什么,我们有时候需要知道错误的具体原因以及是否能恢复。IO
库定义了一个与机器无法的iostate
类型:
-
badbit
:表示系统级错误,一旦badbit
被置位,流一般也无法使用了 -
failbit
:发生可恢复错误时,failbit
被置位,比如期望读取数值却读到一个字符 - 到达文件结束时,
eofbit
和failbit
都会被置位 -
goodbit
:值为0
表示流未发生错误,只要badbit
、eofbit
和failbit
中任一个被置位,则表示发生错误
使用
fail()
和good()
是确定流总体状态的正确方法,而eof
和bad
操作用于确定具体的错误。
3. 管理输出缓冲
每一个输出流都管理一个缓冲区,比如执行输出代码时文本串可以被立即打印出来,也可能被操作系统保存在缓冲区中用于将多个输出操作组合为单一的系统级写操作。
这主要是因为设备的写操作可能很耗时,操作系统将多个输出操作组合成单一的设备写操作可以极大提升性能。
缓冲刷新,即数据真正写到输出设备或文件的原因有如下:
- 程序正常结束:作为
main
函数的return
操作的一部分,执行缓冲刷新 - 缓冲区满时:刷新缓冲方便新的数据写入缓冲区
- 使用操纵符
endl
来显式刷新缓冲区 - 每个输出操作之后,我们可以使用操纵符
unitbuf
设置流的内部状态来清空缓冲区。默认情况下,对cerr
是设置的unitbuf
的,因此写到cerr
的内容都是立即刷新的 - 一个输出流可能被关联到另一个流,在这种情况下读写被关联的流时,关联到的流的缓冲区会被刷新,比如
cin
和cerr
都关联到cout
,读cin
或写cerr
都会导致cout
的缓冲区被刷新
控制缓冲的操纵符:
-
endl
:输出换行符并刷新缓冲区 -
flush
:不附加任何额外字符,刷新缓冲区 -
ends
:输出一个空字符并刷新缓冲区 -
unitbuf
:所有输出操作后都立即刷新缓冲区 -
nounitbuf
:回到正常的缓冲方式
需要注意的是,如果程序崩溃,输出缓冲区不会被刷新,调试一个已经崩溃的程序时,需要确认输出数据是不是因为被挂在缓冲区而没有打印
交互式系统都应该关联输入流和输出流,这意味着所有输出包括用户提示信息都会在读操作之前被打印出来。
文件输入输出
1. 类型及操作
头文件fstream
定义了三个类型来支持文件IO
:
-
ifstream
:从一个给定文件读取数据 -
ofstream
:向一个给定文件写入数据 -
fstream
:读写给定文件
上面提到的类型继承了cin
和cout
操作(比如>>
、<<
和getline
等),fstream
还包括其他特有的操作:
-
fstream fstrm(s);
:创建一个fstream
并打开名为s
的文件,其中s
可以是string
也可以是C
风格字符串指针,这些构造函数都是explict
的 -
fstream fstrm(s, mode);
:和前一个构造函数类似,但按指定模式打开文件 -
fstrm.open(s)
:打开名为s
的文件,并将文件与fstrm
绑定 -
fstrm.close()
:关闭与fstrm
绑定的文件,并返回void
-
fstrm.is_open()
:判断与fstrm
的文件是否成功打开且尚未关闭
2. 使用文件流对象
ifstream in(ifile); // 构造一个ifstream并打开给定文件
ofstream out; // 构造输出文件流,并未关联到任何文件
在要求使用基类型对象的地方,我们可以用继承类型的对象代替,这意味着接受一个iostream
类型引用(或指针)参数的函数可以用一个对应的fstream
或sstream
类型来调用。
如果我们定义了一个空文件流对象,随后可以用open
来将它与文件关联起来:
ifstream in(ifile); // 构造一个ifstream并打开文件
ofstream out; // 输出文件流未与任何文件相关联
out.open(ifile + ".copy"); // 打开指定文件
// 如果调用open失败的话,`failbit`会被置位
if (out) // 检查open是否成功,成功的话我们就可以写入文件
一旦一个文件流已经打开,他就会保持与对应文件的关联,如果对一个恶已经打开的文件流调用
open
会失败,并会导致failbit
被置位,因此文件流关联到另外一个文件时需要先关闭已关联的文件。
3. 自动构造和析构
- 当一个
fstream
对象离开其作用域时,与之关联的文件会自动关闭 - 当一个
fstream
对象被销毁时,close
会自动被调用
4. 文件模式
-
in
:读方式打开 -
out
:写方式打开 -
app
:每次写操作前均定位到文件末尾 -
ate
:打开文件后立即定位到文件末尾 -
trunc
:截断文件 -
binart
:以二进制方式进行IO
与
ifstream
关联的文件默认以in
模式打开,与ofstream
关联的文件默认以out
模式打开,与fstream
关联的文件默认以in
和out
模式打开。
如果我们以out
模式打开文件时文件的内容会被全部丢弃,阻止一个ofstream
清空给定文件内容的方法是同时制定app
模式:
// 下面几条语句中,file1都会被截断
ofstream out("file1"); // 默认以out模式打开
ofstream out("file1", ofstream::out); // 隐含地截断文件
ofstream out("file1", ofstream::out | ofstream::trunc);
// 为了保留文件内容,必须显式指定app模式
ofstream app("file2", ofstream::app); // 隐含为输出模式
ofstream app("file2", ofstream::out | ofstream::app);
保留被
ofstream
打开的文件中已有数据的唯一方法是显式制定app
或in
模式。
string流
-
istringstream
:从string
读取数据 -
ostringstream
:向string
写入数据 -
stringstream
:既可以从string
中读数据,也可以向string
写数据
stringstream
特有的操作包括:
-
sstream strm(s)
:strm
是一个sstream
对象,保存string s
的一个拷贝,此构造函数是explict
的 -
strm.str()
:返回strm
所保存的string
的拷贝 -
strm.str(s)
:将string s
拷贝到strm
中,返回void
网友评论