Team LiB
Previous Section Next Section

8.4. File Input and Output

8.4. 文件的输入和输出

The fstream header defines three types to support file IO:

fstream 头文件定义了三种支持文件 IO 的类型:

  1. ifstream, derived from istream, reads from a file.

    ifstream,由 istream 派生而来,提供读文件的功能。

  2. ofstream, derived from ostream, writes to a file.

    ofstream,由 ostream 派生而来,提供写文件的功能。

  3. fstream, derived from iostream, reads and writes the same file.

    fstream,由 iostream 派生而来,提供读写同一个文件的功能。

The fact that these types are derived from the corresponding iostream types means that we already know most of what we need to know about how to use the fstream types. In particular, we can use the IO operators (<< and >>) to do formatted IO on a file, and the material covered in the previous sections on condition states apply identically to fstream objects.

这些类型都由相应的 iostream 类型派生而来,这个事实意味着我们已经知道使用 fstream 类型需要了解的大部分内容了。特别是,可使用 IO 操作符(<<>> )在文件上实现格式化的 IO,而且在前面章节介绍的条件状态也同样适用于 fstream 对象。

In addition to the behavior that fstream types inherit, they also define two new operations of their ownopen and closealong with a constructor that takes the name of a file to open. These operations can be called on objects of fstream, ifstream, or ofstream but not on the other IO types.

fstream 类型除了继承下来的行为外,还定义了两个自己的新操作—— openclose,以及形参为要打开的文件名的构造函数。fstreamifstreamofstream 对象可调用这些操作,而其他的 IO 类型则不能调用。

8.4.1. Using File Stream Objects

8.4.1. 文件流对象的使用

So far our programs have used the library-defined objects, cin, cout, and cerr. When we want to read or write a file, we must define our own objects, and bind them to the desired files. Assuming that ifile and ofile are strings with the names of the files we want to read and write, we might write code such as

迄今为止,我们的程序已经使用过标准库定义的对象:cincoutcerr。需要读写文件时,则必须定义自己的对象,并将它们绑定在需要的文件上。假设 ifileofile 是存储希望读写的文件名的 strings 对象,可如下编写代码:

    // construct an ifstream and bind it to the file named ifile
    ifstream infile(ifile.c_str());
    // ofstream output file object to write file named ofile
    ofstream outfile(ofile.c_str());

to define and open a pair of fstream objects. infile is a stream that we can read and outfile is a stream that we can write. Supplying a file name as an initializer to an ifstream or ofstream object has the effect of opening the specified file.

上述代码定义并打开了一对 fstream 对象。infile 是读的流,而 outfile 则是写的流。为 ifstream 或者 ofstream 对象提供文件名作为初始化式,就相当于打开了特定的文件。

    ifstream infile;    // unbound input file stream
    ofstream outfile;   // unbound output file stream

These definitions define infile as a stream object that will read from a file and outfile as an object that we can use to write to a file. Neither object is as yet bound to a file. Before we use an fstream object, we must also bind it to a file to read or write:

上述语句将 infile 定义为读文件的流对象,将 outfile 定义为写文件的对象。这两个对象都没有捆绑具体的文件。在使用 fstream 对象之前,还必须使这些对象捆绑要读写的文件:

    infile.open("in");   // open file named "in" in the current directory
    outfile.open("out"); // open file named "out" in the current directory

We bind an existing fstream object to the specified file by calling the open member. The open function does whatever system-specific operations are required to locate the given file and open it for reading or writing as appropriate.

调用 open 成员函数将已存在的 fstream 对象与特定文件绑定。为了实现读写,需要将指定的文件打开并定位,open 函数完成系统指定的所有需要的操作。

Caution: File Names in C++

警告:C++ 中的文件名

For historical reasons, the IO library uses C-style character strings (Section 4.3, p. 130) rather than C++ strings to refer to file names. When we call open or use a file name as the initializer when creating an fstream object, the argument we pass is a C-style string, not a library string. Often our programs obtain file names by reading the standard input. As usual, it is a good idea to read into a string, not a C-style character array. Assuming that the name of the file we wish to use is in a string, we can use the c_str member (Section 4.3.2, p. 139) to obtain a C-style string.

由于历史原因,IO 标准库使用 C 风格字符串(第 4.3 节)而不是 C++ strings 类型的字符串作为文件名。在创建 fstream 对象时,如果调用 open 或使用文件名作初始化式,需要传递的实参应为 C 风格字符串,而不是标准库 strings 对象。程序常常从标准输入获得文件名。通常,比较好的方法是将文件名读入 string 对象,而不是 C 风格字符数组。假设要使用的文件名保存在 string 对象中,则可调用 c_str 成员(第 4.3.2 节)获取 C 风格字符串。


Checking Whether an Open Succeeded
检查文件打开是否成功

After opening a file, it is usually a good idea to verify that the open succeeded:

打开文件后,通常要检验打开是否成功,这是一个好习惯:

    // check that the open succeeded
    if (!infile) {
        cerr << "error: unable to open input file: "
             << ifile << endl;
        return -1;
    }

This condition is similar to those we've used to test whether cin had hit end-of-file or encountered some other error. When we test a stream, the effect is to test whether the object is "okay" for input or output. If the open fails, then the state of the fstream object is that it is not ready for doing IO. When we test the object

这个条件与之前测试 cin 是否到达文件尾或遇到某些其他错误的条件类似。检查流等效于检查对象是否“适合”输入或输出。如果打开(open)失败,则说明 fstream 对象还没有为 IO 做好准备。当测试对象

    if (outfile) // ok to use outfile?

a true return means that it is okay to use the file. Because we want to know if the file is not okay, we invert the return from checking the stream:

返回 true 意味着文件已经可以使用。由于希望知道文件是否未准备好,则对返回值取反来检查流:

    if (!outfile) // not ok to use outfile?

Rebinding a File Stream to a New File
将文件流与新文件重新捆绑

Once an fstream has been opened, it remains associated with the specified file. To associate the fstream with a different file, we must first close the existing file and then open a different file:

fstream 对象一旦打开,就保持与指定的文件相关联。如果要把 fstream 对象与另一个不同的文件关联,则必须先关闭(close)现在的文件,然后打开(open)另一个文件:要点是在尝试打开新文件之前,必须先关闭当前的文件流。open 函数会检查流是否已经打开。如果已经打开,则设置内部状态,以指出发生了错误。接下来使用文件流的任何尝试都会失败。

     ifstream infile("in");      // opens file named "in" for reading
     infile.close();             // closes "in"
     infile.open("next");        // opens file named "next" for reading

Clearing the State of a File Stream
清除文件流的状态

Consider a program that has a vector containing names of files it should open and read, doing some processing on the words stored in each file. Assuming the vector is named files, such a progam might have a loop like the following:

考虑这样的程序,它有一个 vector 对象,包含一些要打开并读取的文件名,程序要对每个文件中存储的单词做一些处理。假设该 vector 对象命名为 files,程序也许会有如下循环:

    // for each file in the vector
    while (it != files.end()) {
        ifstream input(it->c_str());   // open the file;
        // if the file is ok, read and "process" the input
        if (!input)
            break;                  // error: bail out!
        while(input >> s)               // do the work on this file
            process(s);
        ++it;                           // increment iterator to get next file
    }

Each trip through the loop constructs the ifstream named input open to read the indicated file. The initializer in the constructor uses the arrow operator (Section 5.6, p. 164) which dereferences it and fetches the c_str member from the underlying string that it currently denotes. The file is opened by the constructor, and assuming the open succeeded, we read that file until we hit end-of-file or some other error condition. At that point, input is in an error state. Any further attempt to read from input will fail. Because input is local to the while loop, it is created on each iteration. That means that it starts out each iteration in a clean stateinput.good() is true.

每一次循环都构造了名为 inputifstream 对象,打开并读取指定的文件。构造函数的初始化式使用了箭头操作符(第 5.6 节)对 it 进行解引用,从而获取 it 当前表示的 string 对象的 c_str 成员。文件由构造函数打开,并假设打开成功,读取文件直到到达文件结束符或者出现其他的错误条件为止。在这个点上,input 处于错误状态。任何读 input 的尝试都会失败。因为 inputwhile 循环的局部变量,在每次迭代中创建。这就意味着它在每次循环中都以干净的状态即 input.good()true,开始使用。

If we wanted to avoid creating a new stream object on each trip through the while, we might move the definition of input out of the while. This simple change means that we must manage the stream state more carefully. When we encounter end-of-file, or any other error, the internal state of the stream is set so that further reads or writes are not allowed. Closing a stream does not change the internal state of the stream object. If the last read or write operation failed, the state of the object remains in a failure mode until we execute clear to reset the condition of the stream. After the clear, it is as if we had created the object afresh.

如果希望避免在每次 while 循环过程中创建新流对象,可将 input 的定义移到 while 之前。这点小小的改动意味着必须更仔细地管理流的状态。如果遇到文件结束符或其他错误,将设置流的内部状态,以便之后不允许再对该流做读写操作。关闭流并不能改变流对象的内部状态。如果最后的读写操作失败了,对象的状态将保持为错误模式,直到执行 clear 操作重新恢复流的状态为止。调用 clear 后,就像重新创建了该对象一样。

If we wish to reuse an existing stream object, our while loop must remember to close and clear the stream on each trip through the loop:

如果打算重用已存在的流对象,那么 while 循环必须在每次循环进记得关闭(close)和清空(clear)文件流:

    ifstream input;
    vector<string>::const_iterator it = files.begin();
    //   for each file in the vector
    while (it != files.end()) {
        input.open(it->c_str());  // open the file
        // if the file is ok, read and "process" the input
        if (!input)
            break;                    // error: bail out!
        while(input >> s) // do the work on this file
            process(s);
        input.close();        // close file when we're done with it
        input.clear();        // reset state to ok
        ++it;                 // increment iterator to get next file
    }

Had we neglected the call to clear, this loop would read only the first file. To see why, consider what happens in this loop: First we open the indicated file. Assuming open succeeded, we read the file until we hit end-of-file or some other error condition. At that point, input is in an error state. If we close but do not clear the stream, then any subsequent input operation on input will fail. Once we have closed the file, we can open the next one. However, the read of input in the inner while will failafter all, the last read from this stream hit end-of-file. The fact that the end-of-file was on a different file is irrelevant!

如果忽略 clear 的调用,则循环只能读入第一个文件。要了解其原因,就需要考虑在循环中发生了什么:首先打开指定的文件。假设打开成功,则读取文件直到文件结束或者出现其他错误条件为止。在这个点上,input 处于错误状态。如果在关闭(close)该流前没有调用 clear 清除流的状态,接着在 input 上做的任何输入运算都会失败。一旦关闭该文件,再打开 下一个文件时,在内层 while 循环上读 input 仍然会失败——毕竟最后一次对流的读操作到达了文件结束符,事实上该文件结束符对应的是另一个与本文件无关的其他文件。

If we reuse a file stream to read or write more than one file, we must clear the stream before using it to read from another file.

如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用 clear 清除该流的状态。



Exercises Section 8.4.1

Exercise 8.6:

Because ifstream inherits from istream, we can pass an ifstream object to a function that takes a reference to an istream. Use the function you wrote for the first exercise in Section 8.2 (p. 291) to read a named file.

由于 ifstream 继承了 istream,因此可将 ifstream 对象传递给形参为 istream 引用的函数。使用第 8.2 节第一个习题编写的函数读取已命名的文件。

Exercise 8.7:

The two programs we wrote in this section used a break to exit the while loop if the open failed for any file in the vector. Rewrite these two loops to print a warning message if a file can't be opened and continue processing by getting the next file name from the vector.

本节编写的两个程序,在打开 vector 容器中存放的任何文件失败时,使用 break 跳出 while 循环。重写这两个循环,如果文件无法打开,则输出警告信息,然后从 vector 中获取下一个文件名继续处理。

Exercise 8.8:

The programs in the previous exercise can be written without using a continue statement. Write the program with and without using a continue.

上一个习题的程序可以不用 continue 语句实现。分别使用或不使用 continue 语句编写该程序。

Exercise 8.9:

Write a function to open a file for input and read its contents into a vector of strings, storing each line as a separate element in the vector.

编写函数打开文件用于输入,将文件内容读入 string 类型的 vector 容器,每一行存储为该容器对象的一个元素。

Exercise 8.10:

Rewrite the previous program to store each word in a separate element.

重写上面的程序,把文件中的每个单词存储为容器的一个元素。


8.4.2. File Modes

8.4.2. 文件模式

Whenever we open a fileeither through a call to open or as part of initializing a stream from a file namea file mode is specified. Each fstream class defines a set of values that represent different modes in which the stream could be opened. Like the condition state flags, the file modes are integral constants that we use with the bitwise operators (Section 5.3, p. 154) to set one or more modes when we open a given file. The file stream constructors and open have a default argument (Section 7.4.1, p. 253) to set the file mode. The value of the default varies based on the type of the stream. Alternatively, we can supply the mode in which to open the file. Table 8.3 on the next page lists the file modes and their meanings.

在打开文件时,无论是调用 open 还是以文件名作为流初始化的一部分,都需指定文件模式(file mode)。每个 fstream 类都定义了一组表示不同模式的值,用于指定流打开的不同模式。与条件状态标志一样,文件模式也是整型常量,在打开指定文件时,可用位操作符(第 5.3 节)设置一个或多个模式。文件流构造函数和 open 函数都提供了默认实参(第 7.4.1 节)设置文件模式。默认值因流类型的不同而不同。此外,还可以显式地以模式打开文件。表 8.3 列出了文件模式及其含义。

Table 8.3. File Modes
表 8.3 文件模式

in

open for input

打开文件做读操作

out

open output

打开文件做写操作

app

seek to the end before every write

在每次写之前找到文件尾

ate

seek to the end immediately after the open

打开文件后立即将文件定位在文件尾

trunc

truncate an existing stream when opening it

打开文件时清空已存在的文件流

binary

do IO operations in binary mode

以二进制模式进行 IO 操作


The modes out, trunc, and app may be specifed only for files associated with an ofstream or an fstream; in may be specified only for files associated with either ifstream or fstream. Any file may be opened in ate or binary mode. The ate mode has an effect only at the open: Opening a file in ate mode puts the file at the end-of-file immediately after the open. A stream opened in binary mode processes the file as a sequence of bytes; it does no interpretation of the characters in the stream.

outtruncapp 模式只能用于指定与 ofstreamfstream 对象关联的文件;in 模式只能用于指定与 ifstreamfstream 对象关联的文件。所有的文件都可以用 atebinary 模式打开。ate 模式只在打开时有效:文件打开后将定位在文件尾。以 binary 模式打开的流则将文件以字节序列的形式处理,而不解释流中的字符。

By default, files associated with an ifstream are opened in in mode, which is the mode that permits the file to be read. Files opened by an ofstream are opened in out mode, which permits the file to be written. A file opened in out mode is truncated: All data stored in the file is discarded.

默认时,与 ifstream 流对象关联的文件将以 in 模式打开,该模式允许文件做读的操作:与 ofstream 关联的文件则以 out 模式打开,使文件可写。以 out 模式打开的文件会被清空:丢弃该文件存储的所有数据。

In effect, specifying out mode for an ofstream is equivalent to specifying both out and trunc.

从效果来看,为 ofstream 对象指定 out 模式等效于同时指定了 outtrunc 模式。



The only way to preserve the existing data in a file opened by an ofstream is to specify app mode explicitly:

对于用 ofstream 打开的文件,要保存文件中存在的数据,唯一方法是显式地指定 app 模式打开:

    //  output mode by default; truncates file named "file1"
    ofstream outfile("file1");
    // equivalent effect: "file1" is explicitly truncated
    ofstream outfile2("file1", ofstream::out | ofstream::trunc);
    //  append mode; adds new data at end of existing file named "file2"
    ofstream appfile("file2", ofstream::app);

The definition of outfile2 uses the bitwise OR operator (Section 5.3, p. 154) to open inOut in both out and trunc mode.

outfile2 的定义使用了按位或操作符(第 5.3 节)将相应的文件同时以 outtrunc 模式打开。

Using the Same File for Input and Output
对同一个文件作输入和输出运算

An fstream object can both read and write its associated file. How an fstream uses its file depends on the mode specified when we open the file.

fstream 对象既可以读也可以写它所关联的文件。fstream 如何使用它的文件取决于打开文件时指定的模式。

By default, an fstream is opened with both in and out set. A file opened with both in and out mode set is not truncated. If we open the file associated with an fstream with out mode, but not in mode specified, then the file is truncated. The file is also truncated if trunc is specified, regardless of whether in is specified. The following definition opens the file copyOut in both input and output mode:

默认情况下,fstream 对象以 inout 模式同时打开。当文件同时以 inout 打开时不清空。如果打开 fstream 所关联的文件时,只使用 out 模式,而不指定 in 模式,则文件会清空已存在的数据。如果打开文件时指定了 trunc 模式,则无论是否同时指定了 in 模式,文件同样会被清空。下面的定义将 copyOut 文件同时以输入和输出的模式打开:

    // open for input and output
    fstream inOut("copyOut", fstream::in | fstream::out);

Appendix A.3.8 (p. 837) discusses how to use a file that is opened for both input and output.

对于同时以输入和输出的模式打开的文件,附录 A.3.8 将讨论其使用方法。

Mode Is an Attribute of a File, Not a Stream
模式是文件的属性而不是流的属性

The mode is set each time a file is opened:

每次打开文件时都会设置模式

    ofstream outfile;
    // output mode set to out, "scratchpad" truncated
    outfile.open("scratchpad", ofstream::out);
    outfile.close();    // close outfile so we can rebind it
    // appends to file named "precious"
    outfile.open("precious", ofstream::app);
    outfile.close();
    // output mode set by default, "out" truncated
    outfile.open("out");

The first call to open specifies ofstream::out. The file named "scratchpad" in the current directory is opened in output mode; the file will be truncated. When we open the file named "precious," we ask for append mode. Any data in the file remains, and all writes are done at the end of the file. When we opened "out," we did not specify an output mode explicitly. It is opened in out mode, meaning that any data currently in "out" is discarded.

第一次调用 open 函数时,指定的模式是 ofstream::out。当前目录中名为“scratchpad”的文件以输出模式打开并清空。而名为“precious”的文件,则要求以添加模式打开:保存文件里的原有数据,所有的新内容在文件尾部写入。在打开“out”文件时,没有明确指明输出模式,该文件则以 out 模式打开,这意味着当前存储在“out”文件中的任何数据都将被丢弃。

Any time open is called, the file mode is set, either explicitly or implicitly. If a mode is not specified, the default value is used.

只要调用 open 函数,就要设置文件模式,其模式的设置可以是显式的也可以是隐式的。如果没有指定文件模式,将使用默认值。



Valid Combinations for Open Mode
打开模式的有效组合

Not all open modes can be specified at once. Some are nonsensical, such as opening a file setting both in and trunc. That would yield a stream we intend to read but that we have truncated so that there is no data to read. Table 8.4 lists the valid mode combinations and their meanings.

并不是所有的打开模式都可以同时指定。有些模式组合是没有意义的,例如同时以 intrunc 模式打开文件,准备读取所生成的流,但却因为 trunc 操作而导致无数据可读。表 8.4 列出了有效的模式组合及其含义。

Table 8.4. File Mode Combinations
表 8.4 文件模式的组合

out

open for output; deletes existing data in the file

打开文件做写操作,删除文件中已有的数据

out | app

open for output; all writes at end of file

打开文件做写操作,在文件尾写入

out | trunc

same as out

out 模式相同

in

open for input

打开文件做读操作

in | out

open for both input and output;
positioned to read the beginning of the file

打开文件做读、写操作,并定位于文件开头处

in | out | trunc

open for both input and output,
deletes existing data in the file

打开文件做读、写操作,删除文件中已有的数据


Any open mode combination may also include ate. The effect of adding ate to any of these modes changes only the initial position of the file. Adding ate to any of these mode combinations positions the file to the end before the first input or output operation is performed.

上述所有的打开模式组合还可以添加 ate 模式。对这些模式添加 ate 只会改变文件打开时的初始化定位,在第一次读或写之前,将文件定位于文件末尾处。

8.4.3. A Program to Open and Check Input Files

8.4.3. 一个打开并检查输入文件的程序

Several programs in this book open a given file for input. Because we need to do this work in several programs, we'll write a function, named open_file, to perform it. Our function takes references to an ifstream and a string. The string holds the name of a file to associate with the given ifstream:

本书有好几个程序都要打开给定文件用输入。由于需要在多个程序里做这件工作,我们编写一个名为 open_file 的函数实现这个功能。这个函数有两个引用形参,分别是 ifstreamstring 类型,其中 string 类型的引用形参存储与指定 ifstream 对象关联的文件名:

    // opens in binding it to the given file
    ifstream& open_file(ifstream &in, const string &file)
    {
        in.close();     // close in case it was already open
        in.clear();     // clear any existing errors
        // if the open fails, the stream will be in an invalid state
        in.open(file.c_str()); // open the file we were given
        return in; // condition state is good if open succeeded
    }

Because we do not know what state the stream is in, we start by calling close and clear to put the stream into a valid state. We next attempt to open the given file. If the open fails, the stream's condition state will indicate that the stream is unusable. We finish by returning the stream, which is either bound to the given file and ready to use or is in an error condition.

由于不清楚流 in 的当前状态,因此首先调用 closeclear 将这个流设置为有效状态。然后尝试打开给定的文件。如果打开失败,流的条件状态将标志这个流是不可用的。最后返回流对象 in,此时,in 要么已经与指定文件绑定起来了,要么处于错误条件状态。

Exercises Section 8.4.3

Exercise 8.11:

In the open_file function, explain why we call clear before the call to open. What would happen if we neglected to make this call? What would happen if we called clear after the open?

对于 open_file 函数,请解释为什么在调用 open 前先调用 clear 函数。如果忽略这个函数调用,会出现什么问题?如果在 open 后面调用 clear 函数,又会怎样?

Exercise 8.12:

In the open_file function, explain what the effect would be if the program failed to execute the close.

对于 open_file 函数,请解释如果程序执行 close 函数失败,会产生什么结果?

Exercise 8.13:

Write a program similar to open_file that opens a file for output.

编写类似 open_file 的程序打开文件用于输出。

Exercise 8.14:

Use open_file and the program you wrote for the first exercise in Section 8.2 (p. 291) to open a given file and read its contents.

使用 open_file 函数以及第 8.2 节第一个习题编写的程序,打开给定的文件并读取其内容。


Team LiB
Previous Section Next Section