You need to pay close attention to the proper design of a file-reading loop. There are several things to test for when reading from a file. First, the program should not try to read past the EOF. The eof() method returns true if the most recent attempt to read data ran into the EOF. Second, the program might encounter a type mismatch. For instance, Listing 6.16 expects a file containing only numbers. The fail() method returns true if the most recent read attempt encountered a type mismatch. (This method also returns true if the EOF is encountered.) Finally, something unexpected may go wrong—for example, a corrupted file or a hardware failure. The bad() method returns true if the most recent read attempt encountered such a problem. Rather than test for these conditions individually, it’s simpler to use the good() method, which returns true if nothing when wrong:
while (inFile.good()) // while input good and not at EOF
{
...
}
Then, if you like, you can use the other methods to determine exactly why the loop terminated:
if (inFile.eof())
cout << "End of file reached.\n";
else if (inFile.fail())
cout << "Input terminated by data mismatch.\n";
else
cout << "Input terminated for unknown reason.\n";
This code comes immediately after the loop so that it investigates why the loop terminated. Because eof() tests just for the EOF and fail() tests for both the EOF and type mismatch, this code tests for the EOF first. That way, if execution reaches the else if test, the EOF has already been excluded, so a true value for fail() unambiguously identifies type mismatch as the cause of loop termination.
It’s particularly important that you understand that good() reports on the most recent attempt to read input. That means there should be an attempt to read input
// standard file-reading loop design
inFile >> value; // get first value
while (inFile.good()) // while input good and not at EOF
{
// loop body goes here
inFile >> value; // get next value
}
You can condense this somewhat by using the fact that the following expression evaluates to inFile and that inFile, when placed in a context in which a bool value is expected, evaluates to inFile.good()—that is, to true or false:
inFile >> value
Thus, you can replace the two input statements with a single input statement used as a loop test. That is, you can replace the preceding loop structure with this:
// abbreviated file-reading loop design
// omit pre-loop input
while (inFile >> value) // read and test for success
{
// loop body goes here
// omit end-of-loop input
}
This design still follows the precept of attempting to read before testing because to evaluate the expression inFile >> value, the program first has to attempt to read a number into value.
Now you know the rudiments of file I/O.
Summary
Programs and programming become more interesting when you introduce statements that guide the program through alternative actions. (Whether this also makes the programmer more interesting is a point you may wish to investigate.) C++ provides the if statement, the if else statement, and the switch statement as means for managing choices. The C++ if statement lets a program execute a statement or statement block conditionally. That is, the program executes the statement or block if a particular condition is met. The C++ if else statement lets a program select from two choices which statement or statement block to execute. You can append additional if else statements to such a statement to present a series of choices. The C++ switch statement directs the program to a particular case in a list of choices.
C++ also provides operators to help in decision making. Chapter 5 discusses the relational expressions, which compare two values. The if and if else statements typically use relational expressions as test conditions. By using C++’s logical operators (&&, ||, and !), you can combine or modify relational expressions to construct more elaborate tests. The conditional operator (?:) provides a compact way to choose from two values.
The cctype library of character functions provides a convenient and powerful set of tools for analyzing character input.
Loops and selection statements are useful tools for file I/O, which closely parallels console I/O. After you declare ifstream and ofstream objects and associate them with files, you can use these objects in the same manner you use cin and cout.
With C++’s loops and decision-making statements, you have the tools for writing interesting, intelligent, and powerful programs. But we’ve only begun to investigate the real powers of C++. Next, we’ll look at functions.
Chapter Review
1. Consider the following two code fragments for counting spaces and newlines: