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
2
while (cin >> word)
// 读操作成功…

IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。它的每个位表达了一种IO出现的异常,我们可以通过上述函数来访问每个位的值。

badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了。在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。

管理条件状态

流对象的rdstate成员函数返回一个iostate值,对应流的当前状态,我们可以通过它保存下来流的当前值。还有clear成员函数可以用来复位流的状态。有如下例子:

1
2
3
4
auto old_state = cin.rdstate();
cin.clear();
process_input(cin);
cin.setstate(old_state);

这样就先保存下了cin的状态,重置了cin的状态,正常使用cin后又恢复了之前保存的cin的状态。

输出缓冲

管理输出缓冲

每个输出流都管理一个缓冲区,用来保存程序读写的数据。实际上读写流的操作都操作的是缓冲区,其中输出流会将输出内容写到输出缓冲区,当刷新缓冲区时,内容才被真正输出(写入)。

导致缓冲区刷新的原因主要有:

  • 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
  • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
  • 可以使用操纵符如endl来显式刷新缓冲区。
  • 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。

能够引起缓冲区刷新的操作符有enl、flush和ends:

1
2
3
cout << "hi!" <<endl; // 输出hi和一个换行,然后刷新缓冲区
cout << "hi!" <<flush; // 输出hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi! " <<ends; // 输出hi和一个空字符,然后刷新缓冲区

如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作之后都进行一次flush 操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:

1
2
cout << unitbuf;
cout << nounitbuf;

关联输入输出流

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流

标准库将cout和cin关联在一起,因此下面语句将导致cout的缓冲区被刷新:

1
cin >> ival;

使用输入流的成员函数tie可以管理其关联的输出流。

如果使用不带参数的tie函数,将返回本对象关联的输出流的指针,如果未关联输出流,将返回空指针。

如果使用带参数的tie函数,参数为指向一个输出流的指针,那么将把这个输出流关联到该输入流。

文件输入输出

使用文件流

文件流

头文件 fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。

这些类型提供的操作与我们之前已经使用过的对象cin和cout的操作一样。

定义文件流

如下方式可以定义一个文件流:

1
ifstream in(ifile);

ifile是文件名,既可以是string对象又可以是C风格字符串。这样就定义了一个文件输入流并打开了指定文件。

如果像这样定义一个空白的流,就是未绑定(打开)文件的:

1
ofstream out;

打开和关闭文件

如果我们定义了一个空文件流对象,可以随后调用open来将它与文件关联起来:

1
2
ofstream out;
out.open(ifile);

如果调用open失败,failbit会被置位。因为调用open可能失败,通常在进行了open操作之后使用if(out)进行open是否成功的检测。

一个文件只能由一个对象打开,试图打开已打开的文件会失败。为了将文件流关联到另外一个文件,或者让另一个文件流打开该文件,必须首先使用close关闭已经关联的文件:

1
2
in.close();
in.open(ifile2);

此外,当使用带文件名参数来构造文件流时,文件会自动被打开,而不用额外的open操作,且当文件流被销毁时,其析构函数会自动调用close函数

文件模式

指定文件模式

每次调用open时都会确定文件模式,如下例子:

1
2
3
4
5
ofstream out; // 未指定文件打开模式
out.open ("scratchpad"); // 模式隐含设置为输出和截断
out.close();
out.open("precious", ofstream::app); // 模式为输出和追加
out.close();

第一个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
istringstream record(s);

这样就把流record绑定到了string对象s上,就可以像使用cin一样使用record从s中读取数据了。

同样的,定义一个ostringstream的方式如下:

1
ostringstream formatted;

这样可以像使用cout一样去使用formatted,向formatted输出内容,最后使用formatted.str()来将输出的内容获取出来,就是所输出的字符串了。




* 你好,我是大森。如果文章内容帮到了你,你可通过下方付款二维码支持作者 *