if
statement — and two of its repetition
structures the for
and the while
statements.
This lab exercise introduces two more control structures:
switch
selection statementdo-while
repetition statement
and the "forever loop" — a special type of for
loop. Up to now, the only selection structure we have been using is the
if
statement. It is one of C++'s most versatile statements
in that it can be used to implement both two-alternative and multialternative
selections. In the first part of this lab exercise we look at another
selection structure — the switch
statement — that
is more efficient in some cases, but is limited in its applicability.
In Lab 4, we developed a simple 5-function calculator program that allowed the user to select from a menu of operations:
calculate.cpp
that differs from
that in Lab 4 in that the menu options are
labeled a
, b
, c
,
d
, e
instead of +
, -
,
*
, /
, ^
and two functions are used:
getMenuChoice()
: to get the operation selected by the user
apply()
: to apply the operation to the operands
These may seem like rather minor changes and the menu relabeling
even an undesirable one because it is much easier to remember to enter
+
for addition instead of a
,
-
for subtraction instead of b
,
*
for multiplication instead of c
, and so on.
But we will see the reason making this change in the second part of this
lab exercise.
Here is a listing of main()
in calculate.cpp
:
You should download/copyint main() { //*** ENTER YOUR OPENING OUTPUT STATEMENT(S) HERE const string MENU = "\nPlease enter:\n" "\ta - to perform addition;\n" "\tb - to perform subtraction;\n" "\tc - to perform multiplication;\n" "\td - to perform division;\n" "\te - to perform exponentiation;\n" "--> "; char operation = getMenuChoice(MENU); double op1, op2, result; cout << "Now enter your operands: "; cin >> op1 >> op2; assert( cin.good() ); result = apply(operation, op1, op2); cout << "The result is " << result << endl; }
calculate.cpp
into whatever directory/folder/project you are using for this lab exercise.
main()
that
outputs information required by your instructor — e.g., your name,
course, assignment #.
if ... else if ... else
statement to
process the user's menu selection. We now look an alternative way to
implement this multialternative selection structure.
switch
StatementThe multi-branch if
suffers from one drawback:
(operation == '+')
.(operation == '+')
and (operation == '-')
.(operation == '+')
, (operation == '-')
, and (operation == '*')
.Now consider the following general multi-branch if
statement:
if (Condition
1)Statement
1 else if (Condition
2)Statement
2 ... else if (Conditionn
)Statementn
elseStatement
n+1
For Statementi
to be selected, the i
conditions
Condition
1,
Condition
2, ...,
Condition
i-1,
Conditioni
must be evaluated. This can be just what is needed when, for example, it is
necessary to know that all of the tests preceding the
i
th one did, in fact, fail.
On the other hand, the evaluation of each condition takes time;
statements that occur later in the multi-branch if
statement
take longer to execute than statements that occur earlier.
In some situations, synchronization is required; the time required to reach
any of the Statementi
must be the
same for all i
.
We look now at one way this can be avoided — by using a
switch
statement, which has the form
switch (
SelectorExpression
)
{
CaseList
1
StatementList
1
CaseList
2
StatementList
2
...
CaseListn
StatementListn
default:
StatementListn
+1
}
where
SelectorExpression
is any C++ expression
whose value is an integer or is integer-compatible (e.g.,
char
);
StatementListi
is a
sequence of valid C++ statements;
CaseListi
is one or more cases of the form
wherecaseConstant
:
Constant
is an integer-compatible constant.
Pay careful attention to the terminology here.
A switch
statement is executed as follows:
SelectorExpression
is evaluated.
CaseListi
, then execution
begins in StatementListi
and continues until
break
statement,return
statement,switch
statement
SelectorExpression
is not
present in any CaseListi
, then the
(optional) default StatementListn
+1
is executed.
Constant
may appear in more than one
CaseListi
.
Helpful hint: Just as a multi-branch if
should always have a fail-safeelse
clause, you should always have adefault
case in aswitch
statement, even if it simply prints an error message.
Perhaps the trickiest thing about a switch
statement is the
purpose of the cases. They are merely
labels for the compiler to tell it where to start
executing code. Consider this code, where i
is an integer:
switch (i) { case 0: cout << "Zero "; case 1: case 2: cout << "One Two "; default: cout << "Other"; }
Then, if i
is 0, this outputs
IfZero One Two Other
i
is 1 or 2, it outputs
One Two Other
If i
is any other value, it outputs
Other
As this example illustrates, the various cases specify to the
compiler where execution of the code is to begin,
but not when to stop — rather, execution "falls through"
to the end of the switch
.
This is useful in a few situations, but it isn't what we want here.
When we use our 5-function calculator and select option
a
to add two numbers, we don't want to also subtract them,
multiply them, divide them, and raise one to the other power.
To stop executing the code at the end of a statement list in one of the cases,
we can put a break
statement,
a return
statement,
or a call to exit()
at the end of the list; for example,
switch (i) { case 0: cout << "Zero "; break; case 1: case 2: cout << "One Two "; break; default: cout << "Other"; }
Now, if i
is 0, this outputs
and ifZero
i
is 1 or 2, it outputs
and ifOne Two
i
has any other value, the output will be
Other
We have used the return
statement to return a value from a
function; and we have used the exit()
function to stop a program
in case of an error. The break
statement in the preceding
example is even simpler:
break;The following are the key things to remember about using theswitch
statement:Things to Remember:
- The statement list of every case list in a
switch
statement should end with abreak
, areturn
, or anexit()
.
- Each
case
must be a constant.
- The
SelectorExpression
and the case values must all be integer-compatible.
- Finding a matching
case
is done only through equality tests.The integer-compatible data types in C++ include
int
, (of course),short
,long
,unsigned
,char
, andbool
.
It does not includedouble
orstring
.
As a guide: If you find yourself using an algorithm of the form
on the left of chart below where Expression
is
integer-compatible, you can use the switch
in the middle,
which is equivalent to the multi-branch if
on the right:
SetVariable
toExpression
. If (Variable
is equal toValue1
)StatementList1
Else if (Variable
is equal toValue2
)StatementList2
... Else if (Variable
is equal toValuen
)StatementListn
ElseStatementListn+1
End if switch (Expression
) { caseValue1
:StatementList1
break; caseValue2
:StatementList2
break; ... caseValuen
:StatementListn
break; default:StatementListn+1
}Variable
=Expression
; if (Variable
==Value1
) {StatementList1
} else if (Variable
==Value2
) {StatementList2
} ... else if (Variable
==Valuen
) {StatementListn
} else {StatementListn+1
}
A switch
statement is more efficient than the multi-branch
if
statement because a switch
statement can select
any StatementListi
in the time it
takes to evaluate one condition. The switch
statement thus
eliminates the non-uniform execution time of statements controlled by a
multi-branch if
statement. But the restrictions on the
switch
statement do limit its use.
apply()
function in
calculate.cpp
, a switch
has been
partially written — it implements the addition operation. Complete this
switch
statement as directed in the comments.
Use the default case to handle illegal operations by having it output
an error message and return 0. Test your program to
ensure that it works; be sure to test each operator, including illegal ones.
do
and
forever LoopsThe two repetition structures that we used in Lab #4 were
a for
loop and a while
loop. A
for
loop is designed to count through a range of values and we used it
to implement the exponentiation operation. We used a while
loop
to allow the user to repeatedly select a menu option, stopping when a
"special" operation was selected. In this lab exercise, we will look at
two other kinds of loops: a do
loop and a
forever loop.
A loop is categorized by when it evaluates its condition:
The for
loop is a pretest loop, because it evaluates its
condition before the loop's statement is executed. It 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. For many problems, however,
we don't know how many repetitions will be needed.
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.
while
loop provides a general pretest loop.do
loop provides a general posttest
loop.Let's see which of these loops to use to handle menu input.
getMenuChoice()
is defined in calculate.cpp
as follows:
char getMenuChoice(const string MENU) { cout << MENU; char choice; cin >> choice; return choice; }
calculate.cpp
to find out and describe what happens.
We can make our program more user-friendly by handling input errors in this function. One way is to reject bad values entered by the user and to keep on displaying the menu until the user enters a valid menu item. How many repetitions will be needed before this happens isn't something we can predict because we have no idea how many times the user will input bad data.
The general-purpose loops give us a way to handle such user errors, but we must decide which one to use. We can begin by using a generic loop in which we don't worry (for now) how control will leave the loop:
MENU
.choice
.choice
.We have seen how conditions are used in if
and for
statements to alter the default sequential flow of
execution. Similarly, for general purpose loops, there are two
types of conditions we can use:
For our problem, we want the repetition to
choice
is
an invalid selection from the menu, andchoice
is a valid selection.
Since choice
is not known until after
the user enters it, we can't check its validity until after Step (b)
of the preceding generic loop
MENU
.choice
.choice
is a valid menu choice, exit the loop.choice
.do
loop is such a post-test loop;
its pattern is as follows:
When execution reaches such a loop, the following actions occur:do {Statements
} while (Condition
);
Statements
are executed.Condition
is evaluated.Condition
is true, control returns to step 1;
otherwise, control proceeds to the next statement after the loop.do
loop uses a continuation condition.
Also, note that its Condition
is not
evaluated until after the first
execution of Statements
. This means that
a 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 must
make at least one trip through Statements
.
The notion of a "valid" menu choice can be a bit tricky. One
way to make this straightforward is to use consecutive numbers or letters
of the alphabet for our menu selections;
— e.g., 'a'
through 'e'
as in the menu
of operations in this lab.
How does this help? We can pass the first and last valid choices to
getMenuChoice()
as arguments,
and add parameters to thechar operation = getMenuChoice(MENU, 'a', 'e');
getMenuChoice()
's prototype and
definition to store these arguments:
char getMenuChoice(const string MENU, char firstChoice, char lastChoice);
Exer. 7.5. Add the new arguments to the function call in the driver program.
Add the new parameters to the prototype and the definition of
getMenuChoice()
.
Recompile the program to check for errors. However,
the program won't run any differently yet.
Because 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
We are now ready to add achoice < firstChoice || choice > lastChoice
do
loop to the
getMenuChoice()
function to implement our algorithm.
Exer. 7.6. Add ado
loop to the definition of thegetMenuChoice()
function.
- The body of the loop consists of the statements to output
MENU
and inputchoice
.- The declaration of
choice
must be before the loop so that it can be returned after the end of the loop.Compile and test your code.
Another potential source of error occurs when our program reads
values for the operands 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 cin
's
built-in member function good()
.
But suppose we wish to give the user another chance to enter valid
values instead of terminating execution? At first glance, it
looks as though 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:
However, this approach doesn't work well because of two subtleties aboutdo { // prompt for op1 and op2 // input op1 and op2 } while ( !cin.good() );
cin
input (actually, istream
input
in general). When the >>
operator is expecting
a value of a certain type but gets a non-compatible value, two things
happen, each of which causes a difficulty:
cin
fails, its internal
status is set so that its built-in function member
good()
will return false. No input operations can be
performed with cin
so long as
cin.good()
returns false.
The first problem can be fixed with cin.clear()
, which resets
the status of cin
so that cin.good()
returns true
again.
But the second problem is more difficult to fix. We don't want the loop to
continue reading the same bad value over and over and over again, so we
need some way to skip over the bad input. To accomplish this, we can use
cin
's ignore()
member function:
Characters incin.ignore(NumChars
,UntilChar
);
cin
will be skipped until NumChars
have been skipped or until 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
to solve this difficulty:
cin.ignore(120, '\n');
All of this, however, complicates our choice of which loop to use, because we now have four steps to perform:
op1
and op2
. [One or more times.]cin.clear();
[Only if necessary — zero or more
times.]cin.ignore(120, '\n');
[Only if necessary — zero or
more times.]do
loop isn't going to work.
One traditional solution is modify our algorithm to use a pre-test loop in the following way:
op1
and op2
.cin.good()
is false:
cin.clear();
cin.ignore(120, '\n');
op1
and op2
.The C++ pretest loop is the while
loop,
a pretest loop that we have used before in Lab #4. It works like awhile (Condition
)Statement
do
loop except that Condition
is evaluated before the Statement
is evaluated.
If Condition
is false right away, Statement
is
never executed. For this reason, a while
loop is said to
exhibit zero-trip behavior — we might make zero trips
through Statement
.
Exer. 7.3. Replace theassert(cin.good());
statement inmain()
with awhile
loop to to implement the while loop in the preceding algorithm. The body of the loop consists of four statements to implement the four statements in the loop of the algorithm — the first two to fix upcin
and the last two to output a prompt for the real operands and then read them.Compile and test your code.
The algorithm that our program is using is basically as follows:
getMenuChoice()
to get a value for
operation
, guaranteed to be a valid menu operation.op1
and op2
.cin
.op1
and op2
.result
by calling apply()
to apply operation
to
op1
andop2
.result
.To accomplish this, we enclose everything after steps 1 and 2 within a loop as follows:
getMenuChoice()
to get a value for
operation
, guaranteed to be a valid menu operation.op1
and op2
.cin
.op1
and op2
.result
by calling apply()
to apply operation
to
op1
andop2
.result
.In a menu-driven program such as this, a common approach is to add
an option to the menu that the user can select to quit — typically,
the last item on the menu. So we will provide an additional
menu choice 'f'
for quitting. The condition
(operation == 'f')
will be 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 checked; in this case this would be
immediately following the input of operation
. We thus
have a situation where the loop's condition should not be evaluated
at the loop's beginning nor at its end but rather, somewhere
"in the middle" of the loop.
getMenuChoice()
to get a value for
operation
, guaranteed to be a valid menu operation.break
to exit from the loop.op1
and op2
.cin
.op1
and op2
.result
by calling apply()
to apply operation
to
op1
andop2
.result
.In C++, one way to implement an unrestricted loop is with a special form
of the for
loop that we call a forever loop
and that has the following pattern:
By leaving out the three expressions that normally control a
for (;;) // or as some prefer: while(true)
{
StatementList1
if ( Condition
) break;
StatementList2
}for
loop, it becomes an infinite loop.
Of course, we do not want an infinite number
of repetitions; rather, we 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 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.
Exer. 7.8. Inmain()
: (i) first add another item to the menu of operations:and modify the call tof - to quit
getMenuChoice()
appropriately.(ii) then add a forever loop to implement the forever loop in the preceding algorithm.
Compile and test your code.
Note: A forever loop can also be used for the
"valid numeric input" loop that we wrote before. Make that
change in the program, if you like.
Hand In:
Your code and a sample run of your program to demonstrate that everything works — that is, an execution: