8.4. File Input and Output8.4. 文件的输入和输出The fstream header defines three types to support file IO: fstream 头文件定义了三种支持文件 IO 的类型:
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 类型除了继承下来的行为外,还定义了两个自己的新操作—— open 和 close,以及形参为要打开的文件名的构造函数。fstream、ifstream 或 ofstream 对象可调用这些操作,而其他的 IO 类型则不能调用。 8.4.1. Using File Stream Objects8.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 迄今为止,我们的程序已经使用过标准库定义的对象:cin、cout 和 cerr。需要读写文件时,则必须定义自己的对象,并将它们绑定在需要的文件上。假设 ifile 和 ofile 是存储希望读写的文件名的 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 函数完成系统指定的所有需要的操作。
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. 每一次循环都构造了名为 input 的 ifstream 对象,打开并读取指定的文件。构造函数的初始化式使用了箭头操作符(第 5.6 节)对 it 进行解引用,从而获取 it 当前表示的 string 对象的 c_str 成员。文件由构造函数打开,并假设打开成功,读取文件直到到达文件结束符或者出现其他的错误条件为止。在这个点上,input 处于错误状态。任何读 input 的尝试都会失败。因为 input 是 while 循环的局部变量,在每次迭代中创建。这就意味着它在每次循环中都以干净的状态即 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 仍然会失败——毕竟最后一次对流的读操作到达了文件结束符,事实上该文件结束符对应的是另一个与本文件无关的其他文件。
8.4.2. File Modes8.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 文件模式
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. out、trunc 和 app 模式只能用于指定与 ofstream 或 fstream 对象关联的文件;in 模式只能用于指定与 ifstream 或 fstream 对象关联的文件。所有的文件都可以用 ate 或 binary 模式打开。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 模式打开的文件会被清空:丢弃该文件存储的所有数据。
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 节)将相应的文件同时以 out 和 trunc 模式打开。 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 对象以 in 和 out 模式同时打开。当文件同时以 in 和 out 打开时不清空。如果打开 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”文件中的任何数据都将被丢弃。
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. 并不是所有的打开模式都可以同时指定。有些模式组合是没有意义的,例如同时以 in 和 trunc 模式打开文件,准备读取所生成的流,但却因为 trunc 操作而导致无数据可读。表 8.4 列出了有效的模式组合及其含义。 Table 8.4. File Mode Combinations表 8.4 文件模式的组合
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 Files8.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 的函数实现这个功能。这个函数有两个引用形参,分别是 ifstream 和 string 类型,其中 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 的当前状态,因此首先调用 close 和 clear 将这个流设置为有效状态。然后尝试打开给定的文件。如果打开失败,流的条件状态将标志这个流是不可用的。最后返回流对象 in,此时,in 要么已经与指定文件绑定起来了,要么处于错误条件状态。
![]() |