We have seen that the C++ if and switch statements use a condition (an expression that evaluates to true or false) to permit a program to execute statements selectively. For example, we could use the code fragment:
if (op2 != 0)
return (op1 / op2);
else
{
cout << "\nError: Divide by zero attempted !\n";
return 0;
}
and use the condition op2 != 0 to avoid a divide-by-zero error:
if op2 is not equal to zero, then the statement before the
else is executed; otherwise,
the compound statement following the else is executed.
In addition to facilitating selection, most modern programming languages also utilize conditions to permit statements to be executed repeatedly. These repetitive execution statements typically permit a set of statements to be executed over and over, so long as some condition evaluates to true.
As an example use of this capability, suppose that we have a list of lengths that we need to convert from English- to metric-system measurements. Without repetition, converting these lengths requires that we execute the conversion program a separate time for each value on the list. However, by adding a repetitive execution statement to that program, we can convert each value in the list with a single execution of the program.
C++ provides four different loops (well, three, actually) that we will examine in this exercise. These are called the while loop, the do loop, the for loop, and what we call the forever loop.
Today's exercise is to make a calculator program that has more functionality and is more user-friendly than the one we wrote in the last exercise. More precisely, the skeleton program calculate.cpp differs from our previous version by supporting a fifth calculator function: the exponentiation operation. Follow the usual procedure of creating a project for this exercise, save a copy of calculate.cpp there, add calculate.cpp to the project, and then personalize its opening documentation.
You may recall that exponentiation is available in C++ via the function pow() in the cmath library. Just for today (to learn about loops), we will not use pow(), but will instead pretend that we are C++ implementers and write our own exponentiation function, called Power().
Since Power() performs an operation that already exists in a library, we will declare and define it within our source file, rather than in a library.
The exponentiation operation should be a familiar one, since the expression
x^nperforms exponentiation on the base x and the exponent n. We will implement this operation by writing a function Power(), such that a call:
Power(x, n)will compute and return x raised to the power n. To simplify our task, we will assume that n is nonnegative.
As usual, we begin by using object-centered design to carefully design an exponentiation function. However, before we can describe its behavior, some simple analysis may shed light on what our function must do.
Function Analysis. When faced with a new problem, it is often helpful to solve it "by hand." For example, to calculate Power(2,0), Power(2,1), Power(2,3) and Power(2,5) by hand, we might write:
| Power(2,0) | Power(2,1) | Power(2,2) | Power(2,3) |
|---|---|---|---|
| Return 1. | 1 * 2 = 2; | 1 * 2 = 2; | 1 * 2 = 2; |
| Return 2; | 2 * 2 = 4; | 2 * 2 = 4; | |
| Return 4. | 4 * 2 = 8; | ||
| Return 8. |
If we examine the pattern in these "by hand" computations, we might construct the following general solution:
Power(x, n) Initialize result to 1.0. Set result to result * base. - Set result to result * base. | Set result to result * base. | exponent ... | times Set result to result * base. | Set result to result * base. - Return result.This provides us with the information needed to describe our function's behavior.
Function Behavior. We can describe what we want to happen as follows:
Our function should receive a base value and an exponent value from the caller of the function. It should initialize result to one, and then repeatedly multiple result by the base value, with the number of repetitions being the exponent value. Our function should then return result.
Function Objects. From our behavioral description, we can identify the following objects:
| Description | Type | Kind | Movement | Name |
|---|---|---|---|---|
| The base value | double | varying | received | base |
| The exponent value | int | varying | received | exponent |
| The result value | double | varying | received | result |
From this, we can specify the task of our function as follows:
Receive: base, a double;
exponent, an int.
Return: result, a double = base^exponent.
Using this specification, go to calculate.cpp and replace the line
// ... replace this line with the prototype of Power() ...with a prototype of Power(), and replace the line
// ... replace this line with the definition of Power() ...with a function stub for Power(). Then uncomment the call to Power() within Apply() and use the compiler to test the syntax of what you have written.
Note that the call to Power() uses int() to convert the double argument op2 to an integer. Without this conversion, our attempt to pass a double argument into an int parameter would result in a compilation error.
Function Operations. From our behavioral description, we have the following operations:
| Description | Defined? | Name | Library? | |
|---|---|---|---|---|
| 1 | Receive base and exponent | yes | function call mechanism |
built-in |
| 2 | Initialize result to 1.0 | yes | declaration | built-in |
| 3 | Set result to result*base | yes | *= | built-in |
| 4 | Repeat (3), counting from 1 to exponent | yes | for | built-in |
| 5 | Return result | yes | return | built-in |
Function Algorithm. We can organize these operations into the following algorithm:
0. Receive base and exponent from caller.
1. Initialize result to 1.0;
2. For each count from 1 to exponent:
result *= base.
3. Return result.
As we have seen before, the C++ for loop is designed to facilitate the counting behavior required by step 2. Recall that its simplified pattern is:
for (Type Var = Start; Var <= Stop; Var++)
Statement
where Type is a C++ numeric type, Var is the loop control
variable used to do the counting, Start is the first value, and
Stop is the last value.
Using this information, complete the stub of function Power().
Then compile and test calculate.cpp, checking that the
exponentiation works for a variety of values.
The pattern for a C++ for loop is actually more general than we have indicated:
for (InitializationExpr; Condition; StepExpr)
Statement
where InitializationExpr is any initialization expression,
Condition is any boolean expression, and StepExpr is
an arbitrary expression.
For example, if for some reason we wanted to count downwards and
output the multiples of 12 from 100 to 1, then we could write:
for (int i = 100; i >= 1; i--)
cout << 12 * i << endl;
Such a loop will continue to execute so long as the condition i >= 1
evaluates to true.
C++ for loops are thus controlled by conditions, just as if statements are controlled by conditions. As we shall see, each of the other C++ loop statements are also controlled by conditions.
A loop may be categorized by when it evaluates its condition, with respect to the statements it repeats:
The other three C++ loops differ from the for loop in that they are general-purpose loops, designed for problems where the number of repetitions is not known in advance. Why are there three of them? Because
The program in calculate.cpp uses function GetMenuChoice() to get the user's menu choice:
char GetMenuChoice(const string MENU)
{
cout << MENU;
char choice;
cin >> choice;
return choice;
}
Getting a menu choice is a potential source of user error,
because the user could enter an invalid choice.
One way to handle such errors is to repeatedly display the menu and input the user's choice, so long as they continue to enter invalid menu choices. Since there is no way to predict in advance how many erroneous choices the user may enter, the for loop is not an appropriate statement to use in this situation.
The general-purpose loops give us a way to handle user errors, but we must decide which one to use. To do so, we can begin by writing a partial algorithm for this problem using a generic Loop statement, in which we don't worry (for the moment) how control will leave the loop:
Loop:
a. Display MENU.
b. Read choice.
End loop.
Return choice.
Every loop has a continuation condition, that defines the
circumstances under which we want repetition to continue;
and a termination condition, that defines the circumstances
under which repetition should terminate.
(A continuation condition is usually the negation of the termination
condition.)
To determine how control should leave the loop, we must identify
these conditions.
For this particular problem, we want repetition to continue so long as
choice is an invalid menu choiceand terminate when
choice is a valid menu choiceso these are our continuation and termination conditions, respectively.
Since choice is not known until after Step (b), following (b) is the logical place to exit the loop, which we can describe using our termination condition as follows:
Loop:
a. Display MENU.
b. Read choice.
c. If choice is a valid menu choice, exit the loop.
End loop.
Return choice.
In this algorithm, it is apparent that the controlling condition
is evaluated at the bottom of the loop, which implies that a
post-test loop is the appropriate loop to choose.
In C++, the post-test loop is called the do loop, and its pattern is as follows:
do
{
Statements
}
while ( Condition );
When execution reaches such a loop, the following actions occur:
Since its Condition is not evaluated until after the first execution of Statements, the do loop guarantees that Statements will be executed one or more times. For this reason, the do loop is said to exhibit one-trip behavior. We will therefore implement the preceding algorithm using a do loop.
The notion of a "valid" menu choice is a bit tricky. One way to handle it is to require that the valid menu choices be consecutive letters of the alphabet (e.g., 'a' through 'e'). If we then pass the first and last valid choices to GetMenuChoice() as arguments:
char operator = GetMenuChoice(MENU, 'a', 'e');and add parameters to the function prototype and definition to store these arguments:
char GetMenuChoice(const string MENU, char firstChoice, char lastChoice);Since firstChoice and lastChoice will define the range of valid choices, we can express our loop's continuation condition in terms of those values: choice is invalid if and only if
choice < firstChoice || choice > lastChoiceWe are then ready to add a do loop to the function to implement our algorithm:
char GetMenuChoice(const string MENU, char firstChoice, char lastChoice)
{
char choice;
do
{
cout << MENU;
cin >> choice;
}
while (choice < firstChoice || choice > lastChoice);
return choice;
}
Note the necessity of not declaring choice within the loop,
since doing so would make it local to the loop,
and inaccessible by the return statement.
Make the necessary modifications to calculate.cpp so that GetMenuChoice() will only return a valid menu choice. Then thoroughly test your code with invalid and valid values. Continue when it works correctly.
Another potential source of error occurs when our program reads values for op1 and op2 from the keyboard -- the user might enter some non-numeric value. Our current version checks for this using the assert() mechanism and the good() function member of cin:
cout << "\nNow enter your operands: "; double op1, op2; cin >> op1 >> op2; assert(cin.good());But suppose we wished to give the user another chance to enter valid values, instead of just terminating the program? At first glance, it looks like we could use the same approach as before, by replacing the assert() with a posttest loop that repeats the steps so long as good() returns false:
do
{
// prompt for Op1 and Op2
// input Op1 and Op2
}
while ( !cin.good() );
However, this approach is inappropriate because of two subtleties
about numeric I/O.
When the >> operator is expecting a real value, but gets a non-real value,
two things are true, each of which causes a difficulty:
cin.ignore( NumChars, UntilChar );
When execution reaches this statement, characters in cin
will be skipped until NumChars have been skipped,
or the character UntilChar is encountered,
whichever comes first.
Since lines are usually not more than 120 characters in length,
and the end of a line of input is marked by the newline character
('\n'), we can use the following call to solve this difficulty:
cin.ignore(120, '\n');
Such a call will skip 120 characters in cin, or until a
newline character is encountered, whichever comes first.
a. Display a prompt for two real values; // one or more times b. Input op1 and op2; // one or more times c. cin.clear(); // only if necessary (i.e., zero or more times) d. cin,ignore(120, '\n'); // only if necessary (i.e., zero or more times)This poses a dilemma. Some of the steps should be executed at least once, and others zero or more times. Which loop do we choose?
One traditional solution is modify our algorithm to use a pre-test loop in the following way:
a. Display a prompt for two real values;
b. Input op1 and op2;
c. Loop (pretest), so long as cin.good() is false:
1) cin.clear()
2) cin.ignore(120, '\n');
3) Display a prompt for two real values;
4) Input op1 and op2;
End loop.
By performing steps a and b outside of the loop,
we ensure they are executed at least once.
By performing steps 1), 2), 3) and 4) inside the pretest loop,
they will be executed zero times, if the user enters a valid real value
in step b.
The drawback to this approach is its redundance: steps 3) and 4) are exactly the same as steps a and b. This is not too much of an inefficiency, so long as one does not mind the extra typing. In the final part of this exercise, we will see a way to avoid this redundancy.
Like many other languages the C++ pretest loop is called the while loop, and its pattern is as follows:
while ( Condition )
Statement
As usual, Condition is any C++ expression
that evaluates to true or false, and
Statement can be either a single or compound C++ statement.
Statement is often referred to as the body of the loop.
When execution reaches this statement, the following actions occur:
I. Condition is evaluated (before Statement is executed);
II. If Condition evaluates to false, then
Statement is bypassed, and control proceeds to the statement after the loop;
Otherwise
A. Statement is executed; and
B. execution then returns to step I;
End if.
Since Statement may be executed zero or more times,
the while loop is said to exhibit zero-trip behavior
(i.e., the Statement controlled by a while
loop may go unexecuted, if Condition
is initially false).
In calculate.cpp, add a while loop to encode step c of our modified algorithm, as well as the statements to encode steps 1), 2), 3) and 4). Note that the condition
!cin.good()can be used to control the while loop. Note also that since the while loop must repeat multiple statements, its Statement must be a compound statement or else an infinite loop may result.
Then translate and thoroughly test what you have written. If you should happen to generate an infinite loop, it can usually be terminated by typing Ctrl-C (pressing the Ctrl and C keys simultaneously). On some systems, it may be necessary to type Ctrl-C several times, or to press Enter and then Ctrl-C. Continue when what you have written makes the entry of numeric values "fool-proof."
The algorithm that our program is using is basically as follows:
0. Display a greeting.
1. Display a menu of operations and read operation,
guaranteed to be a valid menu operation.
2. Display a prompt for two real values;
3. Read op1 and op2.
4. Loop (pretest), so long as a real value was not entered:
a. Clear the status of cin;
b. Skip the invalid input;
c. Display a prompt for two real values;
d. Input Op1 and Op2;
End loop.
5. Compute result by applying operation to op1 and op2.
6. Display result.
Using this algorithm, we have to re-run our program for every calculation,
which is inconvenient for the user.
A more convenient calculator would "wrap" some of the statements in a loop,
so that the user could perform multiple calculations
without having to re-run the program.
To do so, we might modify our algorithm:
0. Display a greeting.
1. Loop:
a. Display a menu of operations and read operation,
guaranteed to be a valid menu operation.
b. Display a prompt for two real values;
c. Read op1 and op2.
d. Loop (pretest), so long as a real value was not entered:
1) Clear the status of cin;
2) Skip the invalid input;
3) Display a prompt for two real values;
4) Input Op1 and Op2;
End loop.
e. Compute result by applying operation to op1 and op2.
f. Display result.
End loop.
In order to determine which kind of loop to use, we must determine where
to evaluate the loop's termination condition, which we might express as:
the user does not want to quitOne way to have the user indicate that they want to quit is to view quitting as an operation, and provide an additional menu choice (i.e., 'f') by which the user can indicate that they want to quit. The condition
operation == 'f'thus evaluates to true if the user wishes to quit.
So when do we check this condition? The ideal place is to evaluate it as soon as it can be known, namely immediately following the input of operation:
Put differently, if the user wants to quit, steps b through f should not be executed -- execution should immediately leave the loop. We thus have a situation where the loop's condition should not be evaluated at the loop's beginning (eliminating the pretest loop), nor at its end (eliminating the posttest loop), but in its middle (suggesting the unrestricted loop):
0. Display a greeting.
1. Loop:
a. Display a menu of operations and read operation,
guaranteed to be a valid menu operation.
b. If operation is quit, exit the loop.
c. Display a prompt for two real values;
d. Read op1 and op2.
e. Loop (pretest), so long as a real value was not entered:
1) Clear the status of cin;
2) Skip the invalid input;
3) Display a prompt for two real values;
4) Input Op1 and Op2;
End loop.
f. Compute result by applying operation to op1 and op2.
g. Display result.
End loop.
In C++, the unrestricted loop is a simplification of the for
loop that we call the forever loop, that has the following pattern:
for (;;)
{
StatementList1
if ( Condition ) break;
StatementList2
}
That is, by leaving out the three expressions that normally control a
for loop, it becomes an infinite loop.
Of course, we do not want an infinite number of repetitions,
but instead want execution to leave the loop when a termination
condition becomes true.
As shown in the pattern above, this can be accomplished by placing
an if statement that uses a termination condition
to control a break statement
(called an if-break combination)
within the body of the loop.
When a break statement is executed, execution is immediately transferred out of the forever loop to the first statement following the loop. By only selecting the break statement when Condition is true, a forever loop's repetition can be controlled in a manner similar to the other general-purpose loops.
As a result, this loop can be used to solve our problem, by making StatementList1 step a of our algorithm, and making StatementList2 steps b through f. Modify your source program to incorporate this approach, and then translate and test the newly added statements.
Note that this loop can be used to "Get Valid Numeric Input" without redundant statements:
for (;;) // LOOP:
{
cout << "\nEnter two operands: "; // executed one or more times
cin >> op1 >> op2; // executed one or more times
if ( cin.good() ) break; // if numbers entered, exit the loop
cin.clear(); // executed zero or more times
cin.ignore(120, '\n'); // executed zero or more times
} // End Loop.
Because it eliminates redundant coding,
some programmers prefer the forever loop
in situations where some statements must be executed zero or more times
and other statements must be executed one or more times.
Condition, Repetitive Execution, For Loop, While Loop, Do Loop, Forever Loop, General-Purpose Loop, Counting Loop, Zero-Trip Behavior, One-Trip Behavior, Pretest Loop, Posttest Loop, Unrestricted Loop, Break Statement, If-Break Combination.
A hard copy of your final version of calculate.cpp and an execution record showing its execution.
Forward to the Homework Projects