C++学习笔记(8):IO库
IO类
IO库类型
IO库类型主要包含在三个头文件中,iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型,具体如下图:
为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。例如,wcin、wcout和 wcerr是分别对应cin、cout和cerr的宽字符版对象。
类型ifstream和istringstream都继承自istream。因此,我们可以像使用istream对象一样来使用ifstream和istringstream对象。也就是说,我们是如何使用cin的,就可以同样地使用这些类型的对象。例如,可以对一个ifstream或istringstream对象调用getline,也可以使用>>从一个ifstream或istringstream对象中读取数据。
IO对象不能进行拷贝或赋值。
由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
条件状态
查询条件状态
IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。
strm是一种IO类型,提供了一些标志来指示IO流的状态。
以下是IO类所定义的一些标志,用来表示IO流所处的的条件状态:
- strm::iostate:iostate是一种机器相关的类型,提供了表达条件状态的完整功能。
- strm::badbit:用来指出流已崩溃。
- strm::failbit:用来指出一个IO操作失败了。
- strm::eofbit:用来指出流到达了文件结束。
- strm::goodbit:用来指出流未处于错误状态。此值保证为零。
还有一些IO类的成员函数,可以帮助我们判断IO流的这些指示位的条件状态:
- 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):根据给定的flags标志位,将流s中对应条件状态位复位,flags的类型为strm::iostate,返回void。
- s.setstate(flags):根据给定的flags标志位,将流s中对应条件状态位置位,flags的类型为strm::iostate,返回void。
- s.rdstate():返回流s的当前条件状态,返回值类型为strm::iostate。
一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。
由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态,我们可以把读写操作作为while的条件来使用:
1 |
|
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。它的每个位表达了一种IO出现的异常,我们可以通过上述函数来访问每个位的值。
badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了。在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。
管理条件状态
流对象的rdstate成员函数返回一个iostate值,对应流的当前状态,我们可以通过它保存下来流的当前值。还有clear成员函数可以用来复位流的状态。有如下例子:
1 |
|
这样就先保存下了cin的状态,重置了cin的状态,正常使用cin后又恢复了之前保存的cin的状态。
输出缓冲
管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。实际上读写流的操作都操作的是缓冲区,其中输出流会将输出内容写到输出缓冲区,当刷新缓冲区时,内容才被真正输出(写入)。
导致缓冲区刷新的原因主要有:
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
- 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
- 可以使用操纵符如endl来显式刷新缓冲区。
- 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
- 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。
能够引起缓冲区刷新的操作符有enl、flush和ends:
1 |
|
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作之后都进行一次flush 操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
1 |
|
关联输入输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。
标准库将cout和cin关联在一起,因此下面语句将导致cout的缓冲区被刷新:
1 |
|
使用输入流的成员函数tie可以管理其关联的输出流。
如果使用不带参数的tie函数,将返回本对象关联的输出流的指针,如果未关联输出流,将返回空指针。
如果使用带参数的tie函数,参数为指向一个输出流的指针,那么将把这个输出流关联到该输入流。
文件输入输出
使用文件流
文件流
头文件 fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。
这些类型提供的操作与我们之前已经使用过的对象cin和cout的操作一样。
定义文件流
如下方式可以定义一个文件流:
1 |
|
ifile是文件名,既可以是string对象又可以是C风格字符串。这样就定义了一个文件输入流并打开了指定文件。
如果像这样定义一个空白的流,就是未绑定(打开)文件的:
1 |
|
打开和关闭文件
如果我们定义了一个空文件流对象,可以随后调用open来将它与文件关联起来:
1 |
|
如果调用open失败,failbit会被置位。因为调用open可能失败,通常在进行了open操作之后使用if(out)进行open是否成功的检测。
一个文件只能由一个对象打开,试图打开已打开的文件会失败。为了将文件流关联到另外一个文件,或者让另一个文件流打开该文件,必须首先使用close关闭已经关联的文件:
1 |
|
此外,当使用带文件名参数来构造文件流时,文件会自动被打开,而不用额外的open操作,且当文件流被销毁时,其析构函数会自动调用close函数。
文件模式
指定文件模式
每次调用open时都会确定文件模式,如下例子:
1 |
|
第一个open调用未显式指定输出模式,文件隐式地以out模式打开。
通常情况下,out模式意味着同时使用trunc模式,当前目录下名为scratchpad的文件的内容将被清空。当打开名为precious的文件时,我们指定了append模式,文件中已有的数据都得以保留,所有写操作都在文件末尾进行。
以out模式打开文件会丢弃已有数据,因此想要追加数据应当以app模式打开。
文件模式类别
有以下文件模式:
- 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模式打开。
string流
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样。
istringstream从string读取数据,ostringstream向string写入数据,同时有以下操作是string流所特有的:
- strm.str():返回strm所保存的string的拷贝。
- strm.str(s):将string s拷贝到strm中,返回void。
声明一个istringstream的方式如下:
1 |
|
这样就把流record绑定到了string对象s上,就可以像使用cin一样使用record从s中读取数据了。
同样的,定义一个ostringstream的方式如下:
1 |
|
这样可以像使用cout一样去使用formatted,向formatted输出内容,最后使用formatted.str()来将输出的内容获取出来,就是所输出的字符串了。