[C++]C++Primer Chapter 8

IO库

C++语言不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。这些类型支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等。还有一些类型允许内存IO,即,从string读取数据,向string写入数据。

8.1 IO类

IO类型间的关系

类型ifstream和istringstream都继承自istream。因此,我们可以像使用istream对象一样来使用ifstream和istringstream对象。也就是说,我们是如何使用cin的,就可以同样地使用这些类型的对象。例如,可以对一个ifstream或istringstream对象调用getline,也可以使用>>从一个ifstream或istringstream对象中读取数据。类似的,类型ofstream和ostringstream都继承自ostream。因此,我们是如何使用cout的,就可以同样地使用这些类型的对象。

本节剩下部分所介绍的标准库流特性都可以无差别地应用于普通流、文件流和string流,以及char或宽字符流版本。

8.1.1 IO对象无拷贝或赋值

我们不能拷贝或对IO对象赋值。

我们也不能将形参或返回类型设置为流类型(参见6.2.1节,第188页)。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

8.1.2 条件状态

如果我们在标准输入上键入Boo,读操作就会失败。代码中的输入运算符期待读取一个int,但却得到了一个字符B。这样,cin会进入错误状态。类似的,如果我们输入一个文件结束标识,cin也会进入错误状态。

一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。确定一个流对象的状态的最简单的方法是将它当作一个条件来使用

查询流的状态

将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。

IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合来使用

IO库定义了4个iostate类型的constexpr值(参见2.4.4节,第58页),表示特定的位模式。这些值用来表示特定类型的IO条件,可以与位运算符(参见4.8节,第137页)一起使用来一次性检测或设置多个标志位。

badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了。

在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。

如果到达文件结束位置,eofbit和failbit都会被置位。

goodbit的值为0,表示流未发生错误。如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。

操作good在所有错误位均未置位的情况下返回true,而bad、fail和eof则在对应错误位被置位时返回true。此外,在badbit被置位时,fail也会返回true。这意味着,使用good或fail是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()。而eof和bad操作只能表示特定的错误。

管理条件状态

流对象的rdstate成员返回一个iostate值,对应流的当前状态。setstate操作将给定条件位置位,表示发生了对应错误。clear成员是一个重载的成员(参见6.4节,第206页):它有一个不接受参数的版本,而另一个版本接受一个iostate类型的参数。clear不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good会返回true。我们可以这样使用这些成员:

带参数的clear版本接受一个iostate值,表示流的新状态。为了复位单一的条件状态位,我们首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。例如,下面的代码将failbit和badbit复位,但保持eofbit不变:

8.1.3 管理输出缓冲

每个输出流都管理一个缓冲区,用来保存程序读写的数据。

导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:

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

刷新输出缓冲区

我们已经使用过操纵符endl,它完成换行并刷新缓冲区的工作。IO库中还有两个类似的操纵符:flush和ends。flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区

unitbuf操纵符

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

警告:如果程序崩溃,输出缓冲区不会被刷新

关联输入和输出流

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起。

交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。

tie有两个重载的版本(参见6.4节,第206页):一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o)将流x关联到输出流o。

我们既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream:

每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。

8.2 文件输入输出

除了继承自iostream类型的行为之外,fstream中定义的类型还增加了一些新的成员来管理与流关联的文件。在表8.3中列出了这些操作,我们可以对fstream、ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。

8.2.1 使用文件流对象

当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。

创建文件流对象时,我们可以提供文件名(可选的)。如果提供了一个文件名,则open会自动被调用。

用fstream代替iostream&

如果有一个函数接受一个ostream&参数,我们在调用这个函数时,可以传递给它一个ofstream对象,对istream&和ifstream也是类似的。

成员函数open和close

如果调用open失败,failbit会被置位(参见8.1.2节,第280页)。因为调用open可能失败,进行open是否成功的检测通常是一个好习惯。

对一个已经打开的文件流调用open会失败,并会导致failbit被置位。随后的试图使用文件流的操作都会失败。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。

自动构造和析构

当一个fstream对象离开其作用域时,与之关联的文件会自动关闭。在下一步循环中,input会再次被创建。

当一个fstream对象被销毁时,close会自动被调用。

8.2.2 文件模式

每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。

指定文件模式有如下限制:

  • 只可以对ofstream或fstream对象设定out模式。
  • 只可以对ifstream或fstream对象设定in模式。
  • 只有当out也被设定时才可设定trunc模式。
  • 只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
  • 默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作(参见17.5.3节,第676页,将介绍对同一个文件既进行输入又进行输出的方法)。
  • ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。

每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开。

以out模式打开文件会丢弃已有数据

保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式。

每次调用open时都会确定文件模式

对于一个给定流,每当打开文件时,都可以改变其文件模式。

8.3 string流

istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream既可从string读数据也可向string写数据。

除了继承得来的操作,sstream中定义的类型还增加了一些成员来管理与流相关联的string。

8.3.1 使用istringstream

当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用istringstream。

8.3.2 使用ostringstream

当我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。


[C++]C++Primer Chapter 8
https://erlsrnby04.github.io/2024/09/22/C-C-Primer-Chapter-8/
作者
ErlsrnBy04
发布于
2024年9月22日
许可协议