Whenever you want to store information on the computer for longer than the running time of a program, the usual approach is to collect the data into a logically cohesive whole and store it on a permanent storage medium as a file. Ordinarily, a file is stored using magnetic or optical media, such as the hard disk installed inside your computer or a portable flash drive or memory stick. The specific details of the medium, however, are not critical; the important point is that the permanent data objects you store on the computer—documents, games, executable programs, source code, and the like—are all stored in the form of files.
On most systems, files come in a variety of types. For example, in the programming domain, you work with source files, object files, and executable files, each of which has a distinct representation. When you use a file to store data for use by a program, that file usually consists of text and is therefore called a text file. You can think of a text file as a sequence of characters stored in a permanent medium and identified by a file name. The name of the file and the characters it contains have the same relationship as the name of a variable and its contents.
As an example, the following text file contains the first stanza of Lewis Carroll’s
nonsense poem “Jabberwocky,” which appears in Through the Looking Glass:
The name of the file is Jabberwocky.txt, and the contents of the file consist of the four lines of characters that comprise the first stanza of the poem.
When you look at a file, it is often convenient to regard it as a two-dimensional structure: a sequence of lines composed of individual characters. Internally, however, text files are represented as a one-dimensional sequence of characters. In addition to the printing characters you can see, files also contain a newline character that marks the end of each line.
In many respects, text files are similar to strings. Each consists of an ordered collection of characters with a specified endpoint. On the other hand, strings and files differ in several important respects. The most important difference is the permanence of the data. A string is stored temporarily in the computer’s memory during the time that a program runs; a file is stored permanently on a long-term storage device until it is explicitly deleted. There is also a difference in how you refer to individual characters in strings and files. Because each character in a string
Jabberwocky.txt
'Twas brillig, and the slithy toves Did gyre and gimble in the wabe; All mimsy were the borogoves, And the mome raths outgrabe.
has an index, you can process those characters in any order simply by choosing the appropriate sequence of index values. By contrast, characters in a text file tend to be processed sequentially. Programs that work with file data typically start at the beginning of the file and then work their way—either reading existing characters from an input file or writing new ones to an output file—to the end of the file.
Using file streams
As you will discover in section 4.4, the C++ stream library exports several classes that form a hierarchical structure. To help you make sense of that structure as a whole, it is useful to start with two stream classes—ifstream and ofstream— exported by the <fstream> library. Once you are familiar with those examples, it will be easier to generalize from that experience to understand the stream hierarchy as a whole.
The most common methods that apply to file streams appear in Table 4-3. Studying this table, however, is not likely to be as helpful to you as learning a few simple patterns for working with files. File processing in any language tends to be idiomatic in the sense that you need to learn a general strategy and then apply that strategy in the applications you write. C++ is no exception to this rule.
Reading or writing a file in C++ requires the following steps:
1. Declare a stream variable to refer to the file. Programs that work with files typically declare a stream variable for each file that is simultaneously active. Thus, if you are writing a program that reads an input file and uses that data to write an output file, you need to declare two variables, as follows:
ifstream infile; ofstream outfile;
2. Open the file. Before you can use a stream variable, you need to establish an association between that variable and an actual file. This operation is called
opening the file and is performed by calling the stream method open. For example, if you want to read the text contained in the Jabberwocky.txt file, you open the file by executing the method call
infile.open("Jabberwocky.txt");
Because the stream libraries predate the introduction of the string class, the
open method expects a C-style string as the file name. A string literal is therefore acceptable as is. If, however, the name of the file is stored in a
string variable named filename, you will need to open the file like this:
If the requested file is missing, the stream will record that error and let you check for it by calling the predicate method fail. It is your responsibility as a
programmer to recover from such failures, and you will see various strategies for doing so later in this chapter.
T A B L E 4 - 3 Useful methods in the stream classes
Methods supported by all streams
stream.fail() Returns true if the stream is in a failure state. This condition usually occurs when you try to read data past the end of the file, but may also indicate an integrity error in the data.
stream.eof() Returns true if the stream is positioned at the end of the file. Given the
semantics of the C++ stream library, the eof method is useful only after a call to fail. At that point, calling eof allows you to test whether the failure indication was caused by the end of file or some other data error.
stream.clear() Resets the status bits associated with the stream. You need to call this
method whenever you need to reuse a stream after a failure has occurred. Methods supported by all file streams
stream.open(filename) Attempts to open the named file and attach it to the stream. The direction of file transfer is determined by the stream type: input streams are opened for input, output streams are opened for output. The filename parameter is a C-style string, which means that you will need to call c_str on any C++ string. You can check whether the open method fails by calling the fail method.
stream.close() Closes the file attached to the stream.
Methods supported by all input streams
stream >> variable Reads formatted data into a variable. The data format is controlled by the variable type and whatever input manipulators are in effect.
stream.get() Returns the next character in the stream. The return value is an integer,
which makes it possible to identify the end-of-file character, which is represented by the constant EOF.
stream.unget() Backs up the internal pointer of the stream so that the last character read
will be read again by the next call to get.
getline(stream, str) Reads the next line of input from stream into the string str. Methods supported by all output streams
stream << expression Writes formatted data to an output stream. The data format is controlled by the expression type and whatever output manipulators are in effect.
3. Transfer the data. Once you have opened the data files, you then use the appropriate stream operations to perform the actual I/O operations. Depending on the application, you can choose any of several strategies to transfer the file data. At the simplest level, you can read or write files character by character. In some cases, however, it is more convenient to process files line by line. At a still higher level, you can choose to read and write formatted data, which allows you to intermix numeric data with strings and other data types. The details of each of these strategies appear in the sections that follow.
4. Close the file. When you have finished all data transfers, you need to indicate that fact to the file system by calling the stream method close, as follows:
infile.close();
This operation, which is called closing the file, breaks the association between a stream and the actual file.