Section 8.4. File Input and Output


8.4. File Input and Output

The fstream header defines three types to support file IO:

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

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

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

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.

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.

8.4.1. Using File Stream Objects

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

     // 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.

     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.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.

Caution: File Names in 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.


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

     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:

     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:

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

It is essential that we close a file stream before attempting to open a new file. The open function checks whether the stream is already open. If it is open, then it sets its internal state to indicate that a failure has happened. Subsequent attempts to use the file stream will fail.

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:

     // 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.

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.

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:

     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!

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.



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.

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.

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.

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.

Exercise 8.10:

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


8.4.2. File Modes

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.

Table 8.3. File Modes

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


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.

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.

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



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

     //  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.

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.

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:

     // 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.

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.

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.



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.

Table 8.4. File Mode Combinations

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

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.

8.4.3. A Program to Open and Check Input Files

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:

     // 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.

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?

Exercise 8.12:

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

Exercise 8.13:

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

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.




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net