Exercise 3
Repetition Control and I/O Errors
Please note that the material on this website is not intended to be exhaustive. This is intended as a summary and supplementary material to required textbook.
INTRODUCTION
Repetition control (also called loop control or just looping) is widely used in programming to reduce the amount of simple sequential statements. File I/O errors are encountered when inputs (both file input and user input) are not what the program expected.
THE while LOOP
With all looping mechanisms we will have a block of code (enclosed in curly braces) within the loop. This block of code is called the body of the loop. The intention of the loop is to execute the same block of code repetitively. Looping obviates the need to write overlong programs with the same statements executed sequentially.
Some caution must be exercised when using the while loop, and these cautions all relate to the use of the loop control variable or condition. The while loop has the following form:
while (loop_control_condition_is_true) { // the statements in the body of the loop go // here }
Note that there is no semicolon (;) at the end of the loop block and that the body of the loop is contained within curly braces. Note further the loop control condition is always evaluated at the top of the loop.
The number of times that the loop body is executed is determined by the use of the loop control condition, which is a logical expression that must evaluate to true or to false. The loop body may be executed 0 or more times. For instance, the body of the loop is not executed at all if the loop control condition evaluates to false when the loop is encountered in your program. Usually, however, we want the loop to execute at least once. If so, that brings us to the first important caution:
If you want the loop body to execute at least once, you must ensure that the loop control condition evaluates to true when the while loop is encountered in your program. For example:
bool cond = true; ... while (cond) { ... }
Once we are sure that the loop body will execute at least once, we have to pay attention to the second caution:
If you want to avoid an infinite loop (where the loop is never exited) you must ensure that the loop
control condition is made to evaluate false at some point within the body of the loop. For example:
const int MAX = 1000; int i = 0; ... while (i < MAX) { ... i++; }
The loop control condition can be a simple logical expression, or it can be a fairly complicated one. For instance:
while (((i < MAX) && cond) || keep_going) ...
Using the KISS principle (Keep It Simple, Smarty) it is always advisable to keep the loop control condition as simple as possible.
Some programmers are purists about loop control conditions, and believe the while loop should never be exited except at the top of the loop (when the loop control condition becomes false). C++, however, has two statements that allow you to change the normal flow of statements from within the loop: the break and continue statements.
The break statement (when encountered anywhere within the body of the loop) causes the loop to be exited at once:
const int MAX = 1000; bool error = false; int i = 0; ... while (i < MAX) { ... if (error) break; ... i++; }
The continue statement (when encountered in the body of the loop) causes the normal flow of execution to be interrupted by immediately evaluating the loop control condition (at the top of the loop). If the loop control condition is false the loop is exited; if the condition is true execution resumes at the top of the loop body.
Lastly (and this really frosts the loop purists) you can have a loop control condition that is always true and use the break statement to exit the loop:
while (true) { ... if (error) break; ... }
THE for LOOP
THE for LOOP
Many programmers consider the for loop to be "safer" than its while loop counterpart. The for loop has the loop control initialization statement, the loop condition control expression, and the loop control modification statement all at the top of the loop.
for (initialize_loop_control; loop_condition_control; loop_control_modification) { ... // body of loop }
These loops are most often used to run through long lists; for instance, to add up the values of the first 1000 natural numbers, one might use:
int sum = 0; for (int i = 1; i <= 1000; i++) sum += i;
Note that the two statements (the ends) and the expression (in the middle) at the top of the for loop are separated by semicolons (;). The loop control initialization and the loop control modification can have more than one statement, separated by commas (,); for instance:
for (int sum = 0, int i = 1; i <= 1000; i++) sum += i;
Both loop control initialization and loop control modification statements can be empty, as in:
int sum = 0; int i = 1; for ( ; i <= 1000; i++) sum += i;
The loop control condition (in the middle) must be an logical expression; that is, it must evaluate to true or false. The expression can be as complex as you wish, by using the Boolean operators (!, &&, and ||) to construct complex logical expressions.
Lastly, both the break and continue statements (see above) can be used inside the body of the for loop. However, be aware that the continue statement will modify the loop control (using the last part of the for statement) before evaluating the loop control expression. If the loop control expression evaluates to true, the for loop's body continues from the top of the body. If the condition evaluates to false the for loop exits. And, the break statement causes the for loop to be exited immediately.
THE do ... while LOOP
The do ... while loop puts the expression that controls the loop at the bottom, ensuring that the loop will be executed at least once:
do { ... // body of loop } while (loop_condition_is_true);
The do ... while loop is rarely used, as one can almost always figure out how to do the same thing with a for or while loop.
Note that there is a semicolon (;) at the end of the while statement. The break statement in a do ... while loop works as expected: it causes the loop to be exited. However, the use of the continue statement in the do ... while loop should be avoided as Microsoft C++ will not behave as expected (it goes into an infinite loop "somewhere").
Some programming languages (not those derived from C language, of which C++ is one) have a do ... until loop. But, a vast difference distinguishes the two do loops. With the do ... while loop the loop is executed as long as the while condition remains true; with the do ... until loop the loop is executed as long as the until condition is false.
DEALING WITH I/O ERRORS
We have already discussed how to open, read, write, and close files. In this section we will discuss how to deal with I/O errors. Note that a program can encounter an I/O error when reading or writing files, OR when reading user input.
The most commonly encountered errors include (and this list is not necessarily exhaustive):
1. Attempting to open a file for reading when the file does not exist, or is not in the directory specified in the pathname to the file.
2. Attempting to open a file for reading when the user running the program does NOT have permissions to read the file or the directory(s) in its pathname.
3. Attempting to open a file for writing when the file does not exist. (Note: most operating systems will open an empty file, and no error occurs.)
4. Attempting to open a file for writing when the user running the program does NOT have permission to write the file. (Note: different operating systems will deal differently with the situation where the user running the program does not have read and/or write permission on the directories in the file's pathname.)
5. Attempting to read beyond the end-of-file (EOF). 6. Attempting to read a field in an iostream into a variable with the incorrect type.
The first 5 errors are file I/O errors; the last error can be a file OR user input error.
In every error case mentioned above, C++ will FAIL to read or write and will post an error condition in the I/O stream's error bits. The 3 error bits we will discuss are: the fail bit, the bad bit, and the eof bit. When any one or more of these bits are set, the I/O stream has detected an error.
The eof bit is set whenever any previous I/O operation has read the end-of-file indicator; hence, this bit is only set by a read operation. The distinction between the fail bit and the bad bit is a little abstruse. Here is the blurb on them from cplusplus.com:
"failbit is generally set by an input operation when the error was related to the internal logic of the operation itself, so other operations on the stream may be possible. While badbit is generally set when the error involves the loss of integrity of the stream, which is likely to persist even if a different operation is performed on the stream".
The I/O stream library provides several functions that allow you to detect when an I/O operation failed.
The examples shown here are illustrated using cin, but also apply to ANY ifstream as well.
cin.good() – returns true if NONE of the 3 error bits is set; otherwise, it returns false. cin.fail() – returns true if EITHOR the fail bit OR the bad bit is set; otherwise, it returns false. cin.bad() – returns true if ONLY the bad bit is set; otherwise, it returns false. cin.eof() – returns true if the end-of-file indicator has be read in a previous I/O operation; otherwise, it returns false.
Now that we know how to detect an I/O error, the question remains: what do we do about it? Many I/O error should be considered terminal, that is, the program should print out an error message and exit. Errors 1 to 4 above are usually terminal errors.
Reading the end-of-file indicator (error 5 above), is NOT usually considered a terminal error; such an error merely indicates that you can read no further as you have reached the end of the file.
The last error above (6) is probably terminal if you are reading a file, as it indicates that your program is expecting a field of a certain type when the file does not contain a field of that type at the point you are in reading the file. However, when dealing with user input errors, corrective action can be taken.
Such corrective action usually involves printing an error message that explains why the user input is an error, and then re-prompting the user for the correct input. BUT, before you can re-read the user's input, you MUST clear the error bit, AND discard the invalid character you previously attempted to read. Dealing with these I/O errors is not terribly complex, but it can become tiresome.
These errors most often occur when your program is attempting to read a number and it encounters an alpha, so our example will deal with that scenario. Consider the following program.
// Error I/O demonstration program #include <iostream> #include <string> using namespace std;
int main() { int sampleID; float percentC; string yourName; char badChar;
// Loop until user enters 0 for sample ID while (true) { // Get sample ID cout << "Enter sample ID (enter 0 to exit) => " cin >> sampleID; // Check for input error if (cin.fail()) { cin.clear(); cin >> badChar; cout << "Error in data. Invalid character: " << badChar << endl; continue; } // Check for exit if (sampleID == 0) break; cout << "Enter the percent carbon => " cin >> percentC; // Check for error input
if (cin.fail()) { cin.clear(); cin >> badChar; cout << "Error in data. Invalid character: " << badChar << endl; continue; } // Get name cout << "Enter your name => " cin >> yourName;
// Output cout << "Sample ID: " << sampleID << endl; cout << "Percent carbon: " << percentC << endl; cout << "Your name: " << yourName << endl; } // end while true
return 0; }
Here are some sample runs of this program with I/O errors deliberately inserted.
Sample 1 (no errors):
Enter sample ID (enter 0 to exit) => 501 Enter the percent carbon => 15 Enter your name => Martin Sample ID: 501 Percent carbon: 15 Your name: Martin Enter sample ID (enter 0 to exit) => 0
Sample 2:
Enter sample ID (enter 0 to exit) => 5o1 Enter the percent carbon => Error in data. Invalid character: o Enter sample ID (enter 0 to exit) => Enter the percent carbon => 15 Enter your name => Martin Sample ID: 1 Percent carbon: 15 Your name: Martin Enter sample ID (enter 0 to exit) => 0
Sample 3:
Enter sample ID (enter 0 to exit) => 501 Enter the percent carbon => 15% Enter your name => Sample ID: 501 Percent carbon: 15 Your name: % Enter sample ID (enter 0 to exit) => 0
REVIEW EXERCISES
1. *Write a program that adds up the first 100 natural numbers using a while loop. Modify your program to allow the user to enter a natural number (n) and then add up all the natural numbers up to and including n. Insert in your program code that recovers from user entry errors when entering the number.
2. **Write a program that allows the user to enter as many positive (float) numbers as they want (until the user enters 0), and output: the number of numbers entered, the minimum value, maximum value, the range, and the arithmetical mean (average) of the numbers entered (to 2 decimal places).
3. *Redo exercise 1 above using a for loop 4. **Redo exercise 2 above using a do-while loop. 5. ***Write a program that allows the user to enter a beginning positive number and a greater ending
positive number, and then: i) computes and outputs the sum of the squares of all the numbers between the two entered numbers (inclusive), and ii) computes and outputs the sum of the cubes of all the numbers between the entered numbers (inclusive).
© 2011 cad18; rcm27; cpsm