Team LiB
Previous Section Next Section

8.2. File Input and Output

 
Image

The fstream header defines three types to support file IO: ifstream to read from a given file, ofstream to write to a given file, and fstream, which reads and writes a given file. In § 17.5.3 (p. 763) we’ll describe how to use the same file for both input and output.

 

These types provide the same operations as those we have previously used on the objects cin and cout. In particular, we can use the IO operators (<< and >>) to read and write files, we can use getline3.2.2, p. 87) to read an ifstream, and the material covered in § 8.1 (p. 310) applies to these types.

 

In addition to the behavior that they inherit from the iostream types, the types defined in fstream add members to manage the file associated with the stream. These operations, listed in Table 8.3, can be called on objects of fstream, ifstream, or ofstream but not on the other IO types.

 

Table 8.3. fstream-Specific Operations

 
Image
 

8.2.1. Using File Stream Objects

 
Image

When we want to read or write a file, we define a file stream object and associate that object with the file. Each file stream class defines a member function named open that does whatever system-specific operations are required to locate the given file and open it for reading or writing as appropriate.

 

When we create a file stream, we can (optionally) provide a file name. When we supply a file name, open is called automatically:

 

 

ifstream in(ifile); // construct an ifstream and open the given file
ofstream out;       // output file stream that is not associated with any file

 

This code defines in as an input stream that is initialized to read from the file named by the string argument ifile. It defines out as an output stream that is not yet associated with a file. With the new standard, file names can be either library strings or C-style character arrays (§ 3.5.4, p. 122). Previous versions of the library allowed only C-style character arrays.

 
Image
Using an fstream in Place of an iostream&
 

As we noted in § 8.1 (p. 311), we can use an object of an inherited type in places where an object of the original type is expected. This fact means that functions that are written to take a reference (or pointer) to one of the iostream types can be called on behalf of the corresponding fstream (or sstream) type. That is, if we have a function that takes an ostream&, we can call that function passing it an ofstream object, and similarly for istream& and ifstream.

 

For example, we can use the read and print functions from § 7.1.3 (p. 261) to read from and write to named files. In this example, we’ll assume that the names of the input and output files are passed as arguments to main6.2.5, p. 218):

 

 

ifstream input(argv[1]);   // open the file of sales transactions
ofstream output(argv[2]);  // open the output file
Sales_data total;          // variable to hold the running sum
if (read(input, total)) {  // read the first transaction
    Sales_data trans;      // variable to hold data for the next transaction
    while(read(input, trans)) {    // read the remaining transactions
        if (total.isbn() == trans.isbn()) //  check isbns
            total.combine(trans);  // update the running total
        else {
            print(output, total) << endl; //  print the results
            total = trans;         // process the next book
        }
    }
    print(output, total) << endl;  // print the last transaction
} else                             // there was no input
    cerr << "No data?!" << endl;

 

Aside from using named files, this code is nearly identical to the version of the addition program on page 255. The important part is the calls to read and to print. We can pass our fstream objects to these functions even though the parameters to those functions are defined as istream& and ostream&, respectively.

 
The open and close Members
 

When we define an empty file stream object, we can subsequently associate that object with a file by calling open:

 

 

ifstream in(ifile); // construct an ifstreamand open the given file
ofstream out;       // output file stream that is not associated with any file
out.open(ifile + ".copy");  // open the specified file

 

If a call to open fails, failbit is set (§ 8.1.2, p. 312). Because a call to open might fail, it is usually a good idea to verify that the open succeeded:

 

 

if (out)     // check that the open succeeded
    // the open succeeded, so we can use the file

 

This condition is similar to those we’ve used on cin. If the open fails, this condition will fail and we will not attempt to use in.

 

Once a file stream has been opened, it remains associated with the specified file. Indeed, calling open on a file stream that is already open will fail and set failbit. Subsequent attempts to use that file stream will fail. To associate a file stream with a different file, we must first close the existing file. Once the file is closed, we can open a new one:

 

 

in.close();               // close the file
in.open(ifile + "2");     // open another file

 

If the open succeeds, then open sets the stream’s state so that good() is true.

 
Automatic Construction and Destruction
 

Consider a program whose main function takes a list of files it should process (§ 6.2.5, p. 218). Such a program might have a loop like the following:

 

 

// for each file passed to the program
for (auto p = argv + 1; p != argv + argc; ++p) {
    ifstream input(*p);   // create input and open the file
    if (input) {          // if the file is ok, ''process'' this file
        process(input);
    } else
        cerr << "couldn't open: " + string(*p);
} // input goes out of scope and is destroyed on each iteration

 

Each iteration constructs a new ifstream object named input and opens it to read the given file. As usual, we check that the open succeeded. If so, we pass that file to a function that will read and process the input. If not, we print an error message and continue.

 

Because input is local to the while, it is created and destroyed on each iteration (§ 5.4.1, p. 183). When an fstream object goes out of scope, the file it is bound to is automatically closed. On the next iteration, input is created anew.

 

Image Note

When an fstream object is destroyed, close is called automatically.

 

 

Exercises Section 8.2.1

 

Exercise 8.4: 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.

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

Exercise 8.6: Rewrite the bookstore program from § 7.1.1 (p. 256) to read its transactions from a file. Pass the name of the file as an argument to main6.2.5, p. 218).

 

 

8.2.2. File Modes

 
Image

Each stream has an associated file mode that represents how the file may be used. Table 8.4 lists the file modes and their meanings.

 

Table 8.4. File Modes

 
Image
 

We can supply a file mode whenever we open a file—either when we call open or when we indirectly open the file when we initialize a stream from a file name. The modes that we can specify have the following restrictions:

 

out may be set only for an ofstream or fstream object.

 

in may be set only for an ifstream or fstream object.

 

trunc may be set only when out is also specified.

 

app mode may be specified so long as trunc is not. If app is specified, the file is always opened in output mode, even if out was not explicitly specified.

 

• By default, a file opened in out mode is truncated even if we do not specify trunc. To preserve the contents of a file opened with out, either we must also specify app, in which case we can write only at the end of the file, or we must also specify in, in which case the file is open for both input and output (§ 17.5.3 (p. 763) will cover using the same file for input and output).

 

• The ate and binary modes may be specified on any file stream object type and in combination with any other file modes.

 

Each file stream type defines a default file mode that is used whenever we do not otherwise specify a mode. Files associated with an ifstream are opened in in mode; files associated with an ofstream are opened in out mode; and files associated with an fstream are opened with both in and out modes.

 
Opening a File in out Mode Discards Existing Data
 

By default, when we open an ofstream, the contents of the file are discarded. The only way to prevent an ostream from emptying the given file is to specify app:

 

 

// file1 is truncated in each of these cases
ofstream out("file1");   // out and trunc are implicit
ofstream out2("file1", ofstream::out);   // trunc is implicit
ofstream out3("file1", ofstream::out | ofstream::trunc);

// to preserve the file's contents, we must explicitly specify app mode
ofstream app("file2", ofstream::app);   // out is implicit
ofstream app2("file2", ofstream::out | ofstream::app);

 

Image Warning

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

 

 
File Mode Is Determined Each Time open Is Called
 

The file mode of a given stream may change each time a file is opened.

 

 

ofstream out;   // no file mode is set
out.open("scratchpad"); // mode implicitly out and trunc
out.close();    // close out so we can use it for a different file
out.open("precious", ofstream::app);  // mode is out and app
out.close();

 

The first call to open does not specify an output mode explicitly; this file is implicitly opened in out mode. As usual, out implies trunc. Therefore, the file named scratchpad in the current directory 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.

 

Image Note

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

 

 

Exercises Section 8.2.2

 

Exercise 8.7: Revise the bookstore program from the previous section to write its output to a file. Pass the name of that file as a second argument to main.

Exercise 8.8: Revise the program from the previous exercise to append its output to its given file. Run the program on the same output file at least twice to ensure that the data are preserved.


 
Team LiB
Previous Section Next Section