Lab 5: Selective Behavior
Introduction
One of the easiest ways to make a program user-friendly is to
make it menu-driven.
That is, rather than prompting the user in some vague sort of way,
we present them with a menu of the choices available to them.
Then all the user has to do is look at the menu and choose one of the choices.
Since the menu always tells the user what their options are,
the user needs no special knowledge, making such a program easy to use.
For example, a simple 4-function calculator program might prompt the user
by providing the following menu:
Please enter:
+ to add two numbers;
- to subtract two numbers;
* to multiple two numbers; or
/ to divide two numbers.
-->
Thanks to the menu, a user knows exactly what they are to enter,
rather than having to guess.
Today's exercise is to complete such a program, and at the same time
learn about some of the C++ control structures
(statements that control the flow of execution through your program).
Getting Started
Create a 5 subdirectory in your labs directory,
and make it your working directory.
Then save a copy of calculate.cpp.
Using your text editor, personalize its opening documentation.
Then take a few moments to study its structure.
Note the use of assert() to check the preconditions of our program.
Make sure you understand the purpose of each statement in
calculate.cpp befor you continue.
Ignoring its error-checking code,
calculate.cpp prescribes the following behavior:
Our program should display on the screen a greeting, followed
by a menu of permissible operations. It should then read a
user-specified operation from the keyboard. It should then
prompt the user for the (two) operands for that operation,
and then read those operands from the keyboard. It should
then compute the result of applying the user-specified operation
to the two operands. It should conclude by displaying that
result on the screen, with appropriate labeling.
except for the portion given in bold. Since there is no predefined
C++ capability to directly perform that operation, we will write
function Apply() to do so.
Since this function will be closely tied to our calculator problem
(and thus not particularly reuseable), we will store it in
calculate.cpp, rather than in a separately-compiled library.
Function Design
As usual, we use object-centered design to develop this function.
Behavior. From a "high level", our function must apply
operation to op1 and op2 and return the
result.
If we break this task down into its "low level" details,
we can describe the needed behavior as follows:
Our function should receive from its caller an operation,
and two operands. If the operation is '+, our function
should return the sum of the two operands. If the operation
is '-', our function should return their difference. If the
operation is '*', our function should return their product
of the two operands. If the operation is '/', our function
should return their quotient.
Objects. From this behavioral description, we can identify
the following objects:
| Description |
Type |
Kind |
Movement |
Name |
| The operation |
char |
varying |
received |
operation |
| One of the operands |
double |
varying |
received |
op1 |
| The other operand |
double |
varying |
received |
op2 |
| The sum of the operands |
double |
varying |
local |
op1 + op2 |
| The difference of the operands |
double |
varying |
local |
op1 - op2 |
| The product of the operands |
double |
varying |
local |
op1 * op2 |
| The quotient of the operands |
double |
varying |
local |
op1 / op2 |
From this list, we can specify the behavior of our function as follows:
Receive: operation, a char;
op1 and op2, two double values.
Return: the (double) result of applying operation
to op1 and op2.
Using this specification, add a prototype for a function named
Apply() above the main function in calculate.cpp,
and a stub below the main function.
Function Operations
From our behavioral description, we have these operations:
|
Description |
Defined? |
Name |
Library? |
| 1 |
Receive operation (a char) |
yes |
function call
mechanism |
built-in |
| 2 |
Receive op1 (a double) |
yes |
function call
mechanism |
built-in |
| 3 |
Receive op2 (a double) |
yes |
function call
mechanism |
built-in |
| 4 |
Return the sum of op1 and op2 (a double) |
yes |
return, +
|
built-in |
| 5 |
Return the difference of op1 and op2 (a double) |
yes |
return, -
|
built-in |
| 6 |
Return the product of op1 and op2 (a double) |
yes |
return, *
|
built-in |
| 7 |
Return the quotient of op1 and op2 (a double) |
yes |
return, /
|
built-in |
| 8 |
Do exactly one of 4-7, depending on operation |
yes |
?? |
built-in |
As indicated, C++ provides facilities for performing each of these operations.
Operation #8 is different from the others, in that it requires selective
behavior.
One way to elicit selective behavior is by using the C++ if statement.
Such behavior can also be elicited by using the C++ switch statement.
We will compare and contrast these statements in the rest of this exercise.
Function Algorithm.
We can organize these operations into the following algorithm:
0. Receive operation, op1 and op2.
1. If operation is '+':
Return op1 + op2.
Otherwise, if operation is '-':
Return op1 - op2.
Otherwise, if operation is '*':
Return op1 * op2.
Otherwise, if operation is '/':
Return op1 / op2.
End if.
Here, we use a pseudocode form of the if statement
that has multiple branches.
The trick is to see how to code such a form in C++.
Coding 1: Using the if Statement
If you have correctly declared parameters for operation,
op1 and op2 in your prototype and stub,
step 0 of our algorithm should be taken care of.
That just leaves step 1.
As we suggested earlier, each of the "Return ..." parts of step 1
can be performed using a return statement that returns
an appropriate expression.
For example, we can perform the first "Return ..." part with
return op1 + op2;
Add similar return statements for each of the other parts.
Our remaining problem is how to select the appropriate one of these
return statements.
One way to elicit selective behavior is by using the C++ if
statement, whose general pattern is:
if ( Condition ) Statement1 [ else Statement2 ]
Here, if and else are C++ keywords,
Condition is a C++ boolean expression,
and Statement1 and Statement2
are either individual or compound C++ statements
(groups of statements in true braces).
The brackets in the pattern ([ and ]) are used to indicate that the
else Statement2 can be left out.
Note that nothing prevents either Statement1 or Statement2
from being another if statement.
This pattern generates three different forms of the if
statment.
If the else Statement2 is omitted, we get the first form:
if ( Condition )
Statement
which is sometimes called a simple or single-branch if,
since it allows the selective execution of a single section of code.
By contrast, an if statement that has an
else Statement2 portion:
if ( Condition )
Statement1
else
Statement2
is sometimes called a two-branch if,
since it allows the selective execution of either of two sections of code.
A third form of the statement occurs when Statement2
is another if statement:
if ( Condition1 )
Statement1
else if ( Condition2 )
Statement2
...
else if ( ConditionN )
StatementN
else
StatementN+1
This form is called a multi-branch if,
since it allows the selection of any of N+1 different sections of code.
When execution reaches such a statement, Condition1
is evaluated, and if it is true, Statement1 is executed
while the remaining statements are skipped.
However, if Condition1 is false, control proceeds to
the else part where Condition2 is evaluated,
and if it is true, Statement2 is executed
while the remaining statements are skipped.
This behavior continues until either a true condition is found,
or the final condition is determined to be false,
in which case StatementN+1 is executed.
It should be evident that this multi-branch if
provides exactly the behavior we need to perform step 1 of our algorithm:
1. If operation is '+':
Return op1 + op2.
Otherwise, if operation is '-':
Return op1 - op2.
Otherwise, if operation is '*':
Return op1 * op2.
Otherwise, if operation is '/':
Return op1 / op2.
End if.
Add a multi-branch if to your Apply() stub to
encode this step.
Testing and Debugging.
When you are done, translate calculate.cpp into machine language
and test its correctness.
When your program is correct, save calculate.cpp.
Then, since we will be altering this program, use your editor's
File -> Save As...
menu choice to save a copy of this first version under the name
calculate1.cpp, and print a hard copy of it.
(Use a name like calc1.cpp if your operating system
does not permit 10-character file names.)
Then close calculate1.cpp and re-open calculate.cpp.
Coding 2: Using the switch Statement
It should be evident from your testing that the multi-branch if
statement that you just wrote is functionally correct,
in that it solves the problem.
However, it suffers from a drawback:
-
to perform the addition operation,
one condition (
operation == '+') is evaluated;
-
to perform subtraction, two conditions (
operation == '+'
followed by operation == '-') are evaluated;
-
to perform multiplication, three conditions (
operation == '+'
followed by operation == '-' followed by
operation == '*') must be evaluated,
-
and so on.
In general, selecting Statementi using a multi-branch
if statement requires the evaluation of i
conditions.
Since evaluation of each condition consumes time,
statements that occur later in the multi-branch if statement
take longer to execute than do statements that occur earlier.
In certain situations, this penalty can be avoided by using the C++
switch statement, an alternative selective behavior statement.
Its simplified general pattern is:
switch ( ConstantExpression )
{
CaseList1
StatementList1
CaseList2
StatementList2
...
CaseListN
StatementListN
default:
StatementListN+1
}
where ConstantExpression is any C++ expression that
evaluates to a integer-compatible constant;
each StatementListi is a sequence of valid C++ statements,
and each CaseListi is one or more Cases of the form:
case Constant :
where Constant is an integer-compatible constant.
When execution reaches a switch statement,
the following actions occur:
-
The
ConstantExpression is evaluated.
-
If its value is present in
CaseListi, then
execution begins in StatementListi and proceeds,
until a break statement, a return statement,
or the end of the switch statement is encountered.
-
If the value of
ConstantExpression is not present
in any CaseListi, then
the (optional) default StatementListn+1
is executed, if present.
Note that a given Constant value for
ConstantExpression can appear in only one
CaseListi.
Note also that a break statement, whose pattern is
break;
is usually used at the end of each StatementListi,
to force execution to leave the switch statement.
If a break (or return) is not provided,
the C++ switch statement has an interesting drop-through effect,
so that upon reaching the end of StatementListi,
execution proceeds to StatementListi+1.
This can lead to very hard-to-find logic errors, if you forget about it,
or are used to using the similar case statements of other languages.
An important question is: Under what circumstances should a switch statement be used, instead of an if statement?
The answer is that if an algorithm requires selective execution using
multi-branch logic like the following:
If (Variable is equal to Value1) then
StatementList1
Else if (Variable is equal to Value2) then
StatementList2
...
Else if (Variable is equal to ValueN) then
StatementListN
Else
StatementListN+1
End if.
then the algorithm can be encoded using a multi-branch if statement:
if (Variable == Value1)
{
StatementList1
}
else if (Variable == Value2)
{
StatementList2
}
...
else if (Variable == ValueN)
{
StatementListN
}
else
{
StatementListN+1
}
but it is usually more efficient to use a switch
statement with the pattern:
switch (Variable)
{
case Value1:
StatementList1
break;
case Value2:
StatementList2
break;
...
case ValueN:
StatementListN
break;
default:
StatementListN+1
}
The reason the switch statement solution is more efficient
than the multi-branch if statement in such situations
is that where execution of StatementListi
in a multi-branch if requires the evaluation of i
conditions, a switch statement can select
StatementListi in approximately
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.
However, note the restrictions:
-
The
switch can only be used to compare integer-compatible
values (e.g., int, char or bool values).
It cannot be used to compare double or string
values.
-
The
switch can only be used to perform the equality
(or inequality) comparison.
It cannot be used to perform the other relational comparisons
(at least not directly).
Using the pattern above, replace the multi-branch if statement
in function Apply() with a functionally equivalent
(but more efficient) switch statement.
Testing and Debugging.
Translate and test what you have written.
When it is correct, save your work and print a hard copy of
calculate.cpp.
Phrases you should now understand:
Condition, Boolean Expression, Relational Operator, Logical Operator,
Selective Execution, If Statement, Switch Statement.
Submit:
Hard copies of calculate.cpp and calculate1.cpp,
plus an execution record for each.
|
|
|
|
Home
Help
Lab 0
Lab 1
Lab 2
Lab 3a
Lab 3b
Lab 4
Lab 5
Lab 6
Lab 7
Lab 8
Lab 9
Lab 10
Lab 11
Lab 12
Lab 13
Membership
Login |