The Stream Classes
The C++ standard library stream headers contain several different classes that form a class hierarchy, designed using the object-oriented facility known as inheritance.
Note headers, not header. Until now, have one stream header: <iostream>. To read and write to files (instead of the standard streams, we'll use the file stream classes—ifstream and ofstream—found in the <fstream> header. The name ifstream stands for input-file-stream, while the name ofstream stands for output-file-stream.
In the diagram above, each class is a derived class(or subclass), of the class above it. Thus, istream and ostream are both derived from ios, and are specialized kinds of ios objects. In the opposite direction, ios is a base class (or superclass) of both istream and ostream. Similarly, ifstream is derived from istream and ostream is the base class of ofstream.
This relationship—between base and derived classes—is conveyed by the words is a. Every ifstream object is a istream and, by continuing up the hierarchy, an ios. This means that characteristics of any class are inherited by its derived classes.
UML Diagrams
Simple diagrams that show the relationships among classes are useful, but often we want to expand them to include the member functions exposed at each level. This diagram is a standard way of displaying a class hierarchy called the Unified Modeling Language, or UML. In UML, each class appears as a rectangular box whose upper portion contains the name of the class.
In a UML class diagram, the public member functions of the class appear in the lower portion. In UML, derived classes use open arrowheads to point to their base classes.
UML diagrams make it easy to determine which inherited member functions are available to each of the classes in the diagram. Because each class inherits all of the members of every class in its ancestor chain, an object of a particular class can call any member function defined in any of those classes.
For example, the diagram above shows that any ifstream object can call these member functions:
- The open() and close() functions from the ifstream class itself
- The get() and unget() member functions, as well as the >> operator from the istream class
- The clear(), fail(), and eof() functions from the ios class
Easy File I/O
File processing in C++ is fairly straightforward:
- Declare a stream variable to refer to the file. Here's an example with both an input file stream and an output file stream.
- Open the file. To establish an association between that variable and an actual physical file on disk you need to open the file calling open().
ifstream infile;
ofstream outfile;
infile.open("myfile.txt");
Alternatively, you can use perform both steps at once using the stream constructors. Here's an example:
ifstream infile("myfile.txt");
If the file is missing the stream will fail to open; you can check for that by calling the member function fail(). There will be no other error messages:
ifstream infile("myfile.txt";
if (infile.fail())
- Read or write character by character using unformatted I/O.
- Process the file line by line, using line-oriented I/O.
- Read and write formatted data, mixing numeric data with strings and other data types. This is known as token-based file I/O.
Processing Lines
Since text files are usually arranged by lines, it is often useful to read an entire line of data at one time. The easiest way to do that is to use the function named getline() in the <string> library. getline() is not a member function, and it takes two arguments:
- the input stream from which the line is read. (Open the stream as shown in the previous sections.)
- a string variable into which the result is written
By default, getline() stops when it encounters a newline, which is removed from the stream and discarded. It is not stored as part of the string. Like get(), the getline() function returns the input stream, which allows you to test for end-of-file.
string line;
while (getline(in, line))
cout << line << endl;
This while loop reads each line of data from the stream into the string variable named line, until the stream reaches the end of the file. For each line, the body of the loop uses << to send the line to cout, followed by a newline character to replace the one which was discarded by getline().
Processing Tokens
We can also process input token by token or word by word. The word token means "a chunk of meaningful data". A token may be an integer, a number, a string, or a custom type, like stars or points.
As you've already seen, you read a token using the extraction operator >> var. If var is a string, this reads a single word. When var is an int, it reads an integer, and so on.
The input operation returns the stream, just as with raw input and line-oriented input. We could process input word-by-word, like this:
string word;
while (in >> word) // process the word
Of course, the phrase "word-by-word" is not exactly correct, since a "word" may include punctuation, or be a number, and so on. Technically, it is token-by-token.