CS 111 Assignment 7
Tutorial on do/while loops, break statements, and switch statements
- do/while loops.
Compile countUp6.cpp and run it. This program uses a do/while loop instead of either a while loop or a for loop. Similarly, countDown6.cpp is a do/while loop too.A do/while loop is fundamentally different from the other two loops in one crucial respect: the condition is tested at the end of the loop, rather than at the beginning as it is for a while loop or a for loop. This means that a do/while loop's body must always be executed at least once, whereas while loops and for loops allow the option of not executing the body of the loop at all, if the condition is false the first time.
The example programs countUp6.cpp and countDown6.cpp are really more suitable candidates for for loops than for do/while loops, because, in each of these programs, the loop is controlled by one variable which counts from one value to another. However, it is being used as a simple illustration of a do/while loop.
A while loop or a do/while loop is normally used in those situations where the loop is controlled by means more complex than a single variable counting from one value to another. Examples include exercises 2b and 2c above, for which you used while loops.
In such situations, a while loop is used if you want to allow the possibility of not executing the loop at all, if the condition is false the first time, whereas a do/while loop is used if you want to force the loop body to be executed at least once.
- Loop and a half.
Sometimes, we want to force only part of a loop body to be executed at least once. In some of our previous examples of event-controlled repetition, this was accomplished using a while loop, as follows:statements1; while ( condition ) { statements2; statements1; // again }where statements1 and statements2 each consist of one or more statements. Note that statements1 appears twice, once before the loop and once at the end of the loop body, whereas statements2 appears only once. In the following example, statements1 consists of 1 statement, and statements2 consists of two statements:
cin >> number; while ( cin ) { // Add number to cumulative sum: sum = sum + number; count = count + 1; // loop increment // Read NEXT input number: cin >> number; } // whileIf only part of the loop needs to be executed at least once, it is common to use a while loop of the form shown above. The statements which must be executed at least once occur twice in the program, once outside the while loop and again inside. The statements outside the loop are guaranteed to be executed, whereas, when those same statements occur again inside the loop, they might not be executed if the condition is false the first time.
Another way to accomplish this same result, without typing any statements twice in the program, would be to use a do/while loop with a nested if, as follows:
do { statements1; if ( condition ) // same condition as loop condition statements2; } while ( condition );For an example, see copyDoWhile.cpp. An equivalent while loop would look like this:
getline( inputFile, line ); // read the FIRST line, if any while ( inputFile ) { // i.e. while not end-of-file // Output the line most recently read: outputFile << line << endl; // Read NEXT line from input file, if any lines are left: getline( inputFile, line ); } // while not end of fileOn the other hand, in copyDoWhile.cpp, the do/while loop looks like this:
do { getline( inputFile, line ); // read a line from the file if ( inputFile ) { // Output the line most recently read: outputFile << line << endl; } // if } while ( inputFile ); // i.e. while not end-of-fileAlthough the do/while loop version does not require you to type any statements twice, it does require you to type a condition twice, once as the loop condition and once as a branch (if) condition.
Thus, both versions require some code to appear more than once in the program. In the while loop version, at least one statement must appear twice, both at the end of the loop and before the beginning of the loop. On the other hand, in the do/while loop version, the condition appears twice.
Such pairs of identical expressions are sometimes necessary, but are considered undesirable. Suppose that, in debugging a program, you discover that you need to modify such an expression. You then need to remember to modify it on BOTH of the lines on which it occurs in your program. So, it is considered preferable to write everything once, if possible.
The two identical conditions in the do/while loop version are not only an undesirable repetition of code, but they also slow down the program slightly, because the condition now needs to be checked twice per iteration of the loop (once as the loop condition and once as an if condition), rather than just once, as in the while loop version. On the other hand, the presence of a redundant statement in the while loop version does NOT slow the program down at all, because the machine actually executes the steps specified in that statement the same number of times in both versions. Thus, in terms of efficiency of program execution, the while loop version is the lesser of the evils.
So, it is normally considered better programming practice to use a while loop in this kind of situation than to use a do/while loop. Normally, a do/while loop should be used only when you want to guarantee that the entire loop is executed at least once, not just part of it. When it is necessary to guarantee execution of only part of the loop, it is usually preferable to use a while loop.
As we have seen, using a while loop for this purpose does require one or more statements to be typed more than once. However, from the point of view of program execution, a program with a few redundant statements is more efficient than a program which checks an an extra if condition every time the loop is executed.
- break statements.
Another way to write the loop in copyDoWhile.cpp is the following, in copyWhileBreak.cpp:while ( true ) { getline( inputFile, line ); // read a line from the file if ( ! inputFile ) // if end of file break; // Output the line most recently read: outputFile << line << endl; } // whileDespite the always-true loop condition, the above loop avoids being infinite via the break statement, which forces the loop to quit at that point. In effect, this loop is controlled not by the while condition but by the if condition. In general, a break statement causes its most immediate enclosing block (in this case the while loop) to quit. Execution then continues with whatever statements follow the block, in this case whatever statement appears just below the while loop.
The above example has the advantage of not repeating ANY lines of code, neither statements nor conditions. The call to getline does not appear more than once, as it does in LineReturnFixer.java, nor is there a redundant condition, as there is in the do/while version. Because nothing is repeated, this version is easier to modify. It is easier to edit a line of code if you don't have to track down multiple copies of that line and make sure you've edited them all to be consistent. Thus, many programmers would consider the version with the break statement to be the best in terms of programming style.
The loop in copyWhileBreak.cpp is also a little more efficient than the loop in copyDoWhile.cpp. In copyWhileBreak.cpp, the while condition of true is trivial to check; it does not involve an actual comparison, as does the if condition.
However, others would consider the break statement to be bad programming style because it violates the rules of a school of thought known as structured programming, which, among other things, insists that all loops should be controlled by conditions at either the top or the bottom.
In the old days, most programming languages had a commonly-used keyword goto, which allowed program execution to jump from anywhere to anywhere. All program statements were labeled (numbered), and a goto statement consisted of the word goto followed by the label of the statement to which you wanted your program to jump. All this jumping around could easily make a program very hard to read. That's why branching structures (if and if/else) and loops (while, for, and do/while) were invented -- so that goto statements could be eliminated and, instead, a program's jumping around could occur only in predictable, hence easier-to-read patterns.
A furious debate ensued between the adherents of structured programming and other programmers who preferred the flexibility allowed by goto statements. The break statement represents a compromise in this debate. Although it violates what some regard as the overly rigid rules of structured programming, it does so in a manner more constrained, hence more predictable, and hence more readable, than the total freedom allowed by goto statements.
In this course, you may use break statements, and you may use continue statements (see Dale, pp. 474 to 477, in Chapter 9), but you must NOT use goto. C++ does allow goto statements, but newer programming languages such as Java do not, and with good reason.
You are not REQUIRED to use break or continue statements
statements in this course (except for the required breaks in a switch statement, as discussed later in this tutorial). You may, if you prefer, follow strictly the rules of structured programming.
- Appropriate use of while and do/while.
Compile metroNewYorkAreaCodes1.cpp and run it. Enter an area code when prompted. Then, when you are asked:Do you want to enter another area code? (Y/N)>enter 'Y' or 'y' for "yes," and 'N' or 'n' for "no." In addition, see what happens when you enter characters other than 'Y', 'y', 'N', or 'n' in response to that prompt.
Look now a the source code of metroNewYorkAreaCodes1.cpp. It contains three functions: main, decideLocation, and userSaysYes.
In the main function, observe the use of the local bool variable userWantsToContinue to control the following while loop:
bool userWantsToContinue = true; // to force while loop to // execute at least once. while ( userWantsToContinue ) { // Prompt the user cout << "Enter an area code:>"; // Input the area code and check validity: int areaCode; cin >> areaCode; if ( !cin ) { cout << "Error: non-numeric input." << endl; return 1; } // if // Output the corresponding location: string location; if ( decideLocation(areaCode, location) ) cout << location << "." << endl; else cout << areaCode << "? Where on Earth is that?" << endl; // Finish reading the line, before calling userSaysYes: string restOfLine; getline(cin, restOfLine); userWantsToContinue = userSaysYes( // uses cin and cout "Do you want to enter another area code?"); } // whileCompare this with the following, somewhat more concise do/while loop which would do exactly the same thing:
do { // while user wants to continue // Prompt the user cout << "Enter an area code:>"; // Input the area code and check validity: int areaCode; cin >> areaCode; if ( !cin ) { cout << "Error: non-numeric input." << endl; return 1; } // if // Output the corresponding location: string location; if ( decideLocation(areaCode, location) ) cout << location << "." << endl; else cout << areaCode << "? Where on Earth is that?" << endl; // Finish reading the line, before calling userSaysYes: string restOfLine; getline(cin, restOfLine); } while( userSaysYes( // uses cin and cout "Do you want to enter another area code?") );Remember that the key difference between a while loop and a do/while loop is that, in a while loop, the condition (the expression in parentheses next to the word while) is checked before the loop body is executed, whereas, in a do/while loop, the condition is not checked until after the loop is executed. Thus, the body of a do/while loop is always executed at least once, whereas a while loop allows the option of not executing the body of the loop at all. Hence a do/while loop is the best loop to use in those situations where we need to guarantee that a particular repetitive activity will occur at least once, whereas a while loop is better in those situations where we need to allow the option that the loop body not be executed at all.
In both versions of our present example, the loop body is always executed at least once. The do/while loop accomplishes this easily and naturally. With the while loop, on the other hand, some extra steps are needed to guarantee that the loop condition will always be true the first time the loop is executed, so as to ensure that the loop body will be executed at least once.
In the while loop version, a bool variable userWantsToContinue is declared, initialized, and used as the loop condition. Before the loop begins, userWantsToContinue is set to true in order to force the loop to be executed at least once. Then, at the end of the loop, userWantsToContinue is set to the value returned by a call to the function userSaysYes.
On the other hand, in the do/while loop version, the bool variable userWantsToContinue is not needed. Instead, a call to the userSaysYes function is used directly as the loop condition.
The do/while loop version is more concise because this example is the type of situation best suited to a do/while loop, i.e. a situation in which we want the entire body of the loop to be executed at least once.
Compile metroNewYorkAreaCodes2.cpp and run it. Observe that it behaves exactly like metroNewYorkAreaCodes1.cpp. One difference between the two programs is that metroNewYorkAreaCodes2.cpp has a do/while loop instead of the while loop and its accompanying bool variable, which is not needed with the do/while loop.
Both versions of the program call a function userSaysYes, defined further down in the file. Look now at the definition of that function in both versions. The userSaysYes function asks the specified question, prompting the user to reply by entering 'Y' or 'N'. The function returns true if the user answers 'Y' or 'y', false if the user answers 'N' or 'n'. If the user enters some character other than 'Y', 'N', 'y', or 'n', the user is nagged until an appropriate response is obtained.
In metroNewYorkAreaCodes2.cpp, the userSaysYes function contains the following while loop:
while ( line != "Y" && line != "y" && line != "N" && line != "n" ) { cout << "Please enter Y or N:>"; getline(cin, line); } // whilewhereas, in metroNewYorkAreaCodes1.cpp, the userSaysYes function contains the following equivalent do/while loop embedded in an if stqatement:
if ( line != "Y" && line != "y" && line != "N" && line != "n" ) do { cout << "Please enter Y or N:>"; getline(cin, line); } while ( line != "Y" && line != "y" && line != "N" && line != "n" );To behave the same as the while loop, the do/while loop must be nested inside an if structure.
The if condition is identical to the condition in the do/while loop's concluding while clause. The purpose of the surrounding if structure is to allow for the possibility of not executing the loop at all, in the event that the user correctly types an appropriate answer the very first time. Thus, we are dealing with precisely the kind of situation best suited to a while loop. In such a situation, the while loop will be simpler than the equivalent do/while loop.The two versions of the userSaysYes function behave exactly the same, and thus are interchangeable. Thus, they have identical comments, above the function definition heading, describing what the function accomplishes, and describing the flow of information in and out of the function.
In both versions, there are two calls to getline, one inside the loop and one before the loop. Look now at metroNewYorkAreaCodes3.cpp, another program which behaves exactly like metroNewYorkAreaCodes1.cpp and metroNewYorkAreaCodes2.cpp. In this version, a break statement is used inside the while loop in the userSaysYes function, to eliminate the need for two separate calls to getline.
- More about preconditions on cin.
In the comments for the Assignment 5 examples, the preconditions and postconditions for cin and cout were fairly trivial, specifying only that their state must be true. However, for some functions, more substantial preconditions or postconditions for cin or cout are needed.In the comments above the userSaysYes function in all three versions of our area codes program, look now at the comment regarding the global variable cin.
* cin - an istream object for interactive input. * cin is an external variable, declared * in library header file. * Preconditions: The state of cin is true, and * cin is ready to read the beginning of a line. * (Any previous line has been completely read, * including the end-of-line marker.) * Postconditions: The state of cin is still true, * and one or more lines have been completely read. * false otherwise. It is necessary for cin to be "ready to read the beginning of a line" because, otherwise, the first call to getline in the function body would not read the first character on the line, as we need it to do when reading the user's answer.
In the main function, before the call to userSaysYes, there is a call to getline:
// Finish reading the line, before calling userSaysYes: string restOfLine; getline(cin, restOfLine);Earlier in the main function, cin was used as follows:
int areaCode; cin >> areaCode;A call to getline is needed before the call to userSaysYes in order to read (and consume) the end-of-line marker at the end of the line on which areaCode was read. Otherwise, cin will not be ready to read the beginning of the next line when userSaysYes is called.
Try commenting out the call to getline in the main function and see how the program behaves as a result.
- The switch selection structure.
If you have not done so already, read the section of Chapter 9 on the switch statement, in Dale, pp. 462 to 466. In particular, examine her switch statement example on page 465, and compare it with her identically-behaving nested if/else example on the same page. See also Dale's explanation of why the break statements are needed, on page 466.It is not always possible to substitute a switch structure for a nested if/else. Whether a switch can be used depends on what the tested conditions are. A switch can be used only if the conditions involve particular values of a variable or other expression of one of the integer types, i.e. type int, long, short, char, bool, or an enum type. (enum types are discussed later, in Dale, Chapter 10.) Furthermore, the switch structure can be used only to test whether the expression is equal to particular values; it cannot test greater-than or less-than conditions.
For another example of a switch statement and an identically-behaving nested if/else, see the two versions of the decideLocation function in metroNewYorkAreaCodes1.cpp and metroNewYorkAreaCodes2.cpp.
In both versions, the statement dependent on the final else or default case is:
return false;The function quits at this point. The subsequent final return statement:
return true;is executed only if the earlier return statement was NOT executed, i.e. in all cases EXCEPT the final else or default case, for which the earlier return statement was executed.
Look now at metroNewYorkAreaCodes3.cpp. In this version, the switch statement uses return statements directly in all branches of the switch statement, thereby eliminating the need for break statements. Since a return statement makes the entire function quit, it can certainly also make the switch statement quit, too, at the end of a branch. Use of return instead of break is appropriate only if there is nothing else the function needs to do after switch statement has been executed.
Back to: