If we imagine a stream object as a hose, opening the stream is like attaching the ends of the hose to a tap and sprinkler, and the bytes of data moving through the stream object are like the water flowing through the hose. One end of an open stream is attached to the program, and the other is attached to a file. The console I/O objects, cin, cout, and cerr are istream and ostream objects instantiated and connected to the console, which is just a special file, at program startup. Once created and opened (i.e., connected to a file), the file I/O objects behave like cin and cout, implying they support the operators and formatting manipulators described previously.
#include <iostream>
#include <fstream>
using namespace std;
(a)
ifstream
Input File Stream. The "if" stands for input and file, respectively, denoting a stream object from which programs can read data.
ofstream
Output File Stream. The "of" stands for output and file, denoting a stream object to which programs can write data.
fstream
File Stream. The "f" stands for file, but there is no indication of direction; so, it denotes a stream object that allows both reading and writing, although it requires programmers to provide additional configuration.
(b)
(c)
File streams. The meaning or direction of "input" and "output" is always relative to a running program. Input streams read data from a file into a program, and output streams write data from a program into a file. Stream objects treat data as a sequence or stream of bytes, so it is up to the program to group the bytes into meaningful units.
C++ I/O header files. Files in a C++ program that perform I/O must include the appropriate header files. Including fstream also includes iostream, but if the program file also performs console I/O, it should also include iostream for clarity.
The main file I/O stream classes. While the fstream class is more flexible - programs can both read and write files through instances of this class - it is more complicated to use, so we'll focus most of our attention on ifstream and ofstream.
The relation between a program and stream objects. Opening an instance of ifstream attaches one end of the stream to an input file; opening an instance of ofstream attaches one end of the stream to an output file.
As "a file is a named collection of related information" (Silberschatz, Galvin, & Gagne), each file-open operation requires the file's name. Programmers can provide the name in various ways. In the following examples, file_name represents a file's name. In simple cases, the name may be a string literal (a), but it is most often a string variable - either a C-string (b) or an instance of the string class (c). Most operating systems typically do not permit some characters in the directory or file names; the excluded characters depend on the specific system.
"payments.txt"
char* file_name;
string file_name
Flags, Masks, And File Open Modes
Each stream object has several behaviors that control how it treats or processes the data as it moves between the program and the associated file. A one-bit switch or flag governs each behavior. Each flag is like a light switch that is either on, activating the behavior, or off, deactivating it. Streams maintain their total configuration, called their mode, as a set of one-bit flags in a bit vector. Programs establish the file's mode when constructing or opening it and update it with the I/O stream flag functions.
ios::in
Open file for input
ios::out
Open file for output
ios::app
Append data to the end of the output file (the stream ignores subsequent file-pointer repositioning commands, forcing all output to take place at the end of the file)
ios::ate
Open the file at the end of the data (allows the program to reposition the file pointer within the file)
ios::trunc
Truncates or discards the current contents of existing files
ios::binary
Opens the file in binary mode (without this mask, the file is opened in text mode by default)
Named bitmasks for forming file modes.
C++ programs construct file modes from six standard and constant bitmasks defined in I/O system header files. Programs typically access the masks through the ios class with the scope resolution operator, ::, binding the class to the bitmask name.
A bit vector a program can use to open a file with all four behaviors
Bitmask examples.
Each bit in a file's mode corresponds to a specific setting or configuration, but its position in the integer - and, therefore, the integer's magnitude - is irrelevant to its use. Furthermore, the bit positions (and resulting numeric values) are implementation-dependent, suggesting they can vary between platforms. The bitmask names listed in the previous figure are more meaningful than a string of bits and, more significantly, consistent across all ANSI-compliant systems.
The examples, gleaned from one compiler's header files, demonstrate the concept without depending on specific values.
The I/O system defines a pseudo data type, openmode, to represent the named bitmasks and calculated bit vectors. Programs combine the individual bitmasks with the bitwise-OR, creating a bit vector with multiple bits set to 1
A calculated bit vector illustrating multiple bits set to 1, activating or enabling multiple stream behaviors.
Programs use bitmasks and calculated bit vectors to configure stream objects with constructors or the open function.
if (modes & ios::binary)
{
// configure file for binary I/O
}
if (modes & ios::app)
{
// configure file for writing in append mode
}
Interpreting file open modes.
The stream constructors and openfunctions test the stream's configuration with a bitwise-AND operation. modes may be a single openmode bitmask, or it may have multiple bits set with a bitwise-OR expression, as the previous figure illustrates. Each if-statement examines a single mode flag and performs the corresponding actions if it is set.
Opening And Closing Files
Unlike the console stream objects, which are ready for use as soon as the program starts, programs must instantiate the file streams before they can them. The program must also open a file, binding it to the stream and setting the position pointer to the file's beginning. The file stream classes (green) define multiple constructors. Some constructors open the stream as part of the construction process, while others do not. In the latter case, programs open a file with the open function, binding the file to the stream.
Prototypes for some file stream constructors and open functions.
The mode argument has a default value appropriate for a stream class's primary task, making it possible to call the constructors and open functions with a single argument.
ifstream input(file_name);
// use file
// "input" closed by destructor
while (....)
{
ifstream input(file_name);
//use file
// "input" closed by destructor
}
ifstream input;
while (....)
{
input.open(file_name);
//use file
input.close();
}
(a)
(b)
(c)
Opening and closing files.
Open files consume some system resources, limiting the number of files a program can open simultaneously. The file stream destructors automatically close the files when the program destroys a stream object. However, programmers must exercise caution when opening files in a loop to avoid exhausting the limited number of allowed open files.
In a simple and common situation, a stream constructor builds the stream object and opens the named file for processing. When the program finishes processing the file, it can explicitly call the close member function, but the programs call the destructor, closing the file, when it destroys the stream object.
The loop creates, uses, and destroys a stream object during each iteration, incurring the overhead of creating and destroying multiple objects.
The code fragment creates a stream object outside the loop, leaving it unbound to any file. The program opens a file in the loop, uses it, and closes it before the next iteration. This organization is efficient, avoiding the overhead of multiple construction and destruction operations.
File Stream Constructors
Stream classes define a default constructor that creates a stream object, but the constructor does not open or associate the stream with a file. The classes also define constructors with two arguments. The first argument is the name of the file the constructor opens and binds to the stream. The second argument is the file's modes, which the constructor sets when opening the file. The second argument has a default value, meaning that programs can call the constructor with one or two parameters.
Instantiating stream objects.
In each example, file_name is the name of the file to open, while input, output, and file are variable names that programmers may change as needed.
Programs read or input data through ifstream objects, e.g., input >> height;. When the file is opened, it must exist, or the open operation fails.
Calling the default constructor creates a file input stream object, but the program must open a file with the open function.
The second constructor call creates a file input stream object and opens the named file.
Programs write or output data through ofstream objects, e.g., output << height;. If the file exists and contains data, then the fate of the existing data depends on the modes parameter when the file is opened.
The default constructor creates an output file stream but does not open a file.
The parameterized constructor creates an output file stream and opens the named file. If the file exists, absent any contradicting mode settings, the constructor discards or truncates the file's previous contents.
Including the ios::app flag in the file's modes causes the stream to retain existing data and append new data at the end of the file.
fstream objects allow programs to intermix read and write operations on the same file. Programs can perform uninterrupted sequences of either reads or writes but must reset the position pointer position pointer with a "seek" operation (described later in the chapter) when switching from one operation to the other. When opened, the file must exist, or the open operation fails.
Creates a file stream object that can read from and write to a file.
Creates a file stream object bound to the named file, but without an additional mode parameter, the stream object can only read from the file. See Random Access later in this chapter for more detail.
The open Function
Programmers can create and open a stream with a single constructor call. Alternatively, they can separate the creation and opening operations with a default constructor and the open function. This observation suggests the following equivalences:
Opens a file in text mode for reading; the file must exist or the open fails
Opens a file in binary mode for reading; the file must exist or the open fails
Opens a file for writing; creates the file if doesn't exist; truncates or discards existing data if the file does exist
Opens a file for writing; creates the file if it doesn't exist; appends the new data at the end of the existing data if the file does exist
Like (d) but opens the file in binary mode
Opens a file stream object for reading and writing
Validating An Open File
Each stream object maintains a set of four one-bit flags that indicate the stream's current state or condition. File operations like opening, reading, or writing can change the stream's state. The ios class defines several member functions, which the stream subclasses inherit, reporting the stream's state based on the current flag settings. In a Java program, most file operations occur inside a try-catch structure, and the program detects errors when an operation throws an exception. C++ takes a different approach, requiring programmers to explicitly test a stream's state flags. When programmers open a file with a stream object, they validate the stream is ready with one of the inherited functions.
The goodbit flag is the most inclusive and the one I typically use. The good function returns true only if there are no errors on the stream - indicating that a newly opened file is ready for reading or writing. If any of the error or end-of-file flags are set, good returns false. Read and write operations can set two error flags that programmers test with the bad and fail functions. The next section covers the end-of-file bit, eofbit, and the eof function. Please see the "State flag functions" for more detail.
ifstream file(file_name);
if (! file.good())
{
cerr << "Unable to open " << file_name << endl;
exit(1);
//return;
}
// process the file
ifstream file(file_name);
if (file.good())
{
// process the file
}
else
{
cerr << "Unable to open " << file_name << endl;
exit(1);
//return;
}
Testing a file to ensure it opened successfully. There are several reasons that a file may not open successfully:
The individual running the program may not have permission to create or open a file in the specified directory
When creating a new file, if a directory with the same name already exists, the file can not be created or opened
Opening a file for writing will fail if the file is "busy" (that is, open by another program)
When opening a file for reading, if the named file doesn't exist, the open operation fails
Although there are many ways to validate that a file is ready for use, the above code fragments illustrate two common patterns for testing any stream - ifstream, ofstream, or fstream.