4.4 文件输入输出流

使用输入输出流类操纵文件比使用C语言中的stdio更容易、更安全。打开一个文件要做的全部工作就是创建一个对象—这是构造函数所做的工作。不需要显式地关闭文件(尽管能使用成员函数close()来关闭文件),因为当对象超出作用域时析构函数会关闭文件。构造一个ifstream对象用于创建默认的输入文件。构造一个ofstream对象用于创建默认的输出文件。一个fstream对象既可以用于输入文件,也可以用于输出文件。

下图说明了适用于输入输出流类的文件流类:

4.4 文件输入输出流 - 图1

和以前一样,这里实际使用的类都是由类型定义的模板的特化。例如,ifstream用来处理char文件,定义如下:

4.4 文件输入输出流 - 图2

4.4.1 一个文件处理的例子

下面这个例子展示了迄今为止所讨论到的所有特性。注意,对<fstream>的包含声明了文件输入输出类。尽管在许多平台上,这样的声明将自动包含<iostream>,但编译器不一定都这样做。如果想写出可移植的代码,则这两个头文件都要包含进来:

4.4 文件输入输出流 - 图3

创建ifstream对象和ofstream对象后都调用了函数assure(),以确保文件被成功打开。在编译器希望产生结果为布尔值的地方,流对象产生了表示成功或失败的值。

第1个while循环演示了两个get()函数的使用。第1个get()读字符到缓冲区,当已经读取了SZ-1个字符或者读取字符过程中遇到了第3个参数(默认为'\n')所规定的字符时,在缓冲区中写入零结束符。函数get()跳过输入流中的结束字符,所以需使用没有参数的get(),通过调用in.get()丢掉结束字符,这个函数读取一个字节,并返回一个int型的值。也可以使用具有两个默认参数的成员函数ignore()。它的第1个参数表示要跳过的字符的数目,默认值为1。第2个参数指明遇到哪个字符时,函数ignore()退出(在提取这个字符之后),默认值为EOF。

紧接着是两个相似的输出语句:一个输出到cout,一个输出到文件out。注意,使用这个方法很方便,不需要担心对象类型,因为格式化语句对所有ostream对象都能很好地处理。前一条语句把一行显示到标准输出上,第2条语句将一行以及其行号写入一个新文件中。

为演示函数getline()如何工作,现在打开刚刚创建的文件,跳过行号。在以读方式再次打开文件之前,为了确保正确地关闭这个文件,这里有两种方法可供选择。可以使用花括号把程序的第1部分括起来,从而强制对象out超出作用域,这会使系统调用析构函数并关闭该文件,这个程序就是这样做的。也可以调用成员函数close()关闭这些文件;如果采用这种方法,甚至可以通过调用成员函数open()重用对象in。

第2个while循环语句说明了函数getline()遇到输入流中的结束字符(函数的第3个参数,默认为'\n')时是如何将其除去的。尽管getline()像get()一样在缓冲区中写入字符零,但它不在缓冲区插入结束字符。

这个例子,连同本章的大部分例子,都假定对任何重载函数getline()的调用都会遇到新行界定字符。如果不是这种情况,流的状态位eofbit就会被设置,并且对getline()的调用返回false,造成程序丢失该输入的最后一行字符。

4.4.2 打开模式

通过覆盖构造函数的默认参数,可以控制文件的打开模式。下表说明了控制文件打开模式的标志:

4.4 文件输入输出流 - 图4

位掩码(bitwise)或运算符可以使用这些标记的组合。

二进制标记只在一些非UNIX系统上起作用,例如从MS-DOS发展而来的操作系统,这些系统对行结束界定符有特殊约定。例如,在MS-DOS系统的文本模式(默认模式)下,每输出一个换行符('\n'),文件系统实际上输出了两个字符,一个回车符/换行符(CRLF)对,ASCII码为0x0D和0x0A。相反,当在文本模式下读这样的文件到内存,遇到这样的字节对时,就在相应的位置写入一个'\n'来替代。如果想绕过这个特殊的处理过程,可以采用二进制模式打开文件。在二进制模式下,不论是否写生僻的字节到文件,都没有需要特殊处理的事情,总是能进行操作(通过调用write())。然而,应该在使用read()或write()的时候以二进制模式打开文件,因为这些函数有一个每次读/写字节个数的参数。在这些情况下,如果流中包含额外字符'\r',字节个数参数将不再起作用。如果想使用本章后面将要讨论的流指针定位命令,则也应该使用二进制模式打开文件。

可以通过声明fstream对象打开一个文件,同时用作输入和输出。声明fstream对象的时候,必须使用足够的打开模式标记,告诉文件系统现在想进行输入、输出或既输入又输出。从输出切换到输入的时候,需要刷新流或者重定位文件指针。从输入切换到输出,也需要重定位文件指针。通过fstream对象创建一个文件,在构造函数中使用ios:trunc打开模式标志,可以调用输入和输出文件。