|
course.wilkes.edu/CS125Labs |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Lab 6: Repetitive Execution
Introduction
We have seen that the C++
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
Getting Started
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
calculate2.cpp
differs from our previous version by supporting a fifth calculator function:
the exponentiation operation.
Follow the usual procedure of creating a
You may recall that exponentiation is available in C++ via the function
Since
The Exponentiation OperationThe 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.
Function DesignAs 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
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:
From this, we can specify the task of our function as follows: Receive: base, a
Using this specification, go to // ... 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 Function Operations. From our behavioral description, we have the following operations:
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.
Coding
As we have seen before, the C++
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.
Characterizing Loops
The pattern for a C++
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++ A loop may be categorized by when it evaluates its condition, with respect to the statements it repeats:
for loop is a pretest loop,
because it evaluates its condition before the loop's statement is executed
(don't take my word for it -- check back and see!)
However, the for loop is designed primarily for problems that
involve counting through ranges of numbers, or problems in which the
number of repetitions can be determined in advance.
As we shall see, there are many problems that do not fit this pattern.
The other three C++ loops differ from the
Getting a Valid Menu Choice
The program in
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
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:
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:
do loop uses a continuation condition.
Since its
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 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
Getting Valid Numeric Input
Another potential source of error occurs when our program reads values
for 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:
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
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 ( 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 !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
|
Home Membership
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Last update: Friday, March 23, 2001 at 11:22:38 AM. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||