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 getline
(§ 3.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
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 string
s or C-style character arrays (§ 3.5.4, p. 122). Previous versions of the library allowed only C-style character arrays.
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 main
(§ 6.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.
open
and close
MembersWhen 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
.
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.
Exercises Section 8.2.1
Exercise 8.4: Write a function to open a file for input and read its contents into a
vector
ofstring
s, storing each line as a separate element in thevector
.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
main
(§ 6.2.5, p. 218).
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.
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 anofstream
orfstream
object.
•
in
may be set only for anifstream
orfstream
object.
•
trunc
may be set only whenout
is also specified.
•
app
mode may be specified so long astrunc
is not. Ifapp
is specified, the file is always opened in output mode, even ifout
was not explicitly specified.
• By default, a file opened in
out
mode is truncated even if we do not specifytrunc
. To preserve the contents of a file opened without
, either we must also specifyapp
, in which case we can write only at the end of the file, or we must also specifyin
, 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
andbinary
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.
out
Mode Discards Existing DataBy 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);
The only way to preserve the existing data in a file opened by an
ofstream
is to specifyapp
orin
mode explicitly.
open
Is CalledThe 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.
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.