My memory is so bad, that many times i forget my own name!
MIGUEL DE CERVANTES SAAVEDRA, Don Quixote
Recall that the principle of procedural abstraction says that functions should be designed so that they can be used as black boxes. For a programmer to use a function effectively, all the programmer should need to know is the function declaration and the accompanying comment that says what the function accomplishes. The programmer should not need to know any of the details contained in the function body. In this section we discuss a number of topics that deal with this principle in more detail.
A function body may contain a call to another function. The situation for these sorts of function calls is exactly the same as it would be if the function call had occurred in the main
function of the program; the only restriction is that the function declaration should appear before the function is used. If you set up your programs as we have been doing, this will happen automatically, since all function declarations come before the main
function and all function definitions come after the main
function. Although you may include a function call within the definition of another function, you cannot place the definition of one function within the body of another function definition.
Display 5.8 shows an enhanced version of the program shown in Display 5.4. The program in Display 5.4 always reversed the values of the variables firstNum
and secondNum
. The program in Display 5.8 reverses these variables only some of the time. The program in Display 5.8 uses the function order
to reorder the values in these variables so as to ensure that
firstNum <= secondNum
Function Calling Another Function
Sample Dialogue
Enter two integers: 10 5
In increasing order the numbers are: 5 10
If this condition is already true, then nothing is done to the variables firstNum
and secondNum
. If, however, firstNum
is greater than secondNum
, then the function swapValues
is called to interchange the values of these two variables. This testing for order and exchanging of variable values all takes place within the body of the function order
. Thus, the function swapValues
is called within the body of the function order
. This presents no special problems. Using the principle of procedural abstraction, we think of the function swapValues
as performing an action (namely, interchanging the values of two variables); this action is the same no matter where it occurs.
One good way to write a function declaration comment is to break it down into two kinds of information, called a precondition and a postcondition. The precondition states what is assumed to be true when the function is called. The function should not be used and cannot be expected to perform correctly unless the precondition holds. The postcondition describes the effect of the function call; that is, the postcondition tells what will be true after the function is executed in a situation in which the precondition holds. For a function that returns a value, the postcondition will describe the value returned by the function. For a function that changes the value of some argument variables, the postcondition will describe all the changes made to the values of the arguments.
For example, the function declaration comment for the function swapValues
shown in Display 5.8 can be put into this format as follows:
void swapValues(int& variable1, int& variable2);
//Precondition: variable1 and variable2 have been given
//values.
//Postcondition: The values of variable1 and variable2
//have been interchanged.
The comment for the function celsius
from Display 5.2 can be put into this format as follows:
double celsius(double fahrenheit);
//Precondition: fahrenheit is a temperature expressed
//in degrees Fahrenheit.
//Postcondition: Returns the equivalent temperature
//expressed in degrees Celsius.
When the only postcondition is a description of the value returned, programmers often omit the word postcondition. A common and acceptable alternative form for the previous function declaration comments is the following:
//Precondition: fahrenheit is a temperature expressed
//in degrees Fahrenheit.
//Returns the equivalent temperature expressed in
//degrees Celsius.
Another example of preconditions and postconditions is given by the following function declaration:
void postInterest(double& balance, double rate);
//Precondition: balance is a nonnegative savings
//account balance.rate is the interest rate
//expressed as a percent, such as 5 for 5%.
//Postcondition: The value of balance has been
//increased by rate percent.
You do not need to know the definition of the function postInterest
in order to use this function, so we have given only the function declaration and accompanying comment.
Preconditions and postconditions are more than a way to summarize a function’s actions. They should be the first step in designing and writing a function. When you design a program, you should specify what each function does before you start designing how the function will do it. In particular, the function declaration comments and the function declaration should be designed and written down before starting to design the function body. If you later discover that your specification cannot be realized in a reasonable way, you may need to back up and rethink what the function should do, but by clearly specifying what you think the function should do, you will minimize both design errors and wasted time writing code that does not fit the task at hand.
Some programmers prefer not to use the words precondition and postcondition in their function comments. However, whether you use the words or not, your function comment should always contain the precondition and postcondition information.
This case study solves a very simple programming task. It may seem that it contains more detail than is needed for such a simple task. However, if you see the design elements in the context of a simple task, you can concentrate on learning them without the distraction of any side issues. Once you learn the techniques that are illustrated in this simple case study, you can apply these same techniques to much more complicated programming tasks.
We have been commissioned by the Quick-Shop supermarket chain to write a program that will determine the retail price of an item given suitable input. Their pricing policy is that any item that is expected to sell in one week or less is marked up 5 percent, and any item that is expected to stay on the shelf for more than one week is marked up 10 percent over the wholesale price. Be sure to notice that the low markup of 5 percent is used for up to 7 days and that at 8 days the markup changes to 10 percent. It is important to be precise about exactly when a program should change from one form of calculation to a different one.
As always, we should be sure we have a clear statement of the input required and the output produced by the program.
Input
The input will consist of the wholesale price of an item and the expected number of days until the item is sold.
Output
The output will give the retail price of the item.
Like many simple programming tasks, this one breaks down into three main subtasks:
Input the data.
Compute the retail price of the item.
Output the results.
These three subtasks will be implemented by three functions. The three functions are described by their function declarations and accompanying comments, which are given below. Note that only those items that are changed by the functions are call-by-reference parameters. The remaining formal parameters are call-by-value parameters.
void getInput(double& cost, int& turnover);
//Precondition: User is ready to enter values correctly.
//Postcondition: The value of cost has been set to the
//wholesale cost of one item. The value of turnover has been
//set to the expected number of days until the item is sold.
double price(double cost, int turnover);
//Precondition: cost is the wholesale cost of one item.
//turnover is the expected number of days
//until sale of the item.
//Returns the retail price of the item.
void giveOutput(double cost, int turnover, double price);
//Precondition: cost is the wholesale cost of one item;
//turnover is the expected time until sale of the item;
//price is the retail price of the item.
//Postcondition: The values of cost, turnover, and price have
//been written to the screen.
Now that we have the function headings, it is trivial to write the main
part of our program:
int main()
{
double wholesaleCost, retailPrice;
int shelfTime;
getInput(wholesaleCost, shelfTime);
retailPrice = price(wholesaleCost, shelfTime);
giveOutput(wholesaleCost, shelfTime, retailPrice);
return 0;
}
Even though we have not yet written the function bodies and have no idea of how the functions work, we can write the above code that uses the functions. That is what is meant by the principle of procedural abstraction. The functions are treated like black boxes.
The implementations of the functions getInput
and giveOutput
are straightforward. They simply consist of a few cin
and cout
statements. The algorithm for the function price
is given by the following pseudocode:
if turnover ≤ 7 days then
return (cost +5% of cost);
else
return (cost +10% of cost);
There are three constants used in this program: a low markup figure of 5 percent, a high markup figure of 10 percent, and an expected shelf stay of 7 days as the threshold above which the high markup is used. Since these constants might need to be changed to update the program should the company decide to change its pricing policy, we declare global named constants at the start of our program for each of these three numbers. The declarations with the const
modifier are the following:
const double LOW_MARKUP = 0.05; //5%
const double HIGH_MARKUP = 0.10; //10%
const int THRESHOLD = 7; //Use HIGH_MARKUP if do not
//expect to sell in 7 days or less
The body of the function price
is a straightforward translation of our algorithm from pseudocode to C++ code:
{
if (turnover <= THRESHOLD)
return ( cost + (LOW_MARKUP * cost) );
else
return ( cost + (HIGH_MARKUP * cost) );
}
The complete program is shown in Display 5.9.
Supermarket Pricing
1 //Determines the retail price of an item according to
2 //the pricing policies of the Quick-Shop supermarket chain.
3 #include <iostream>
4 const double LOW_MARKUP = 0.05; //5%
5 const double HIGH_MARKUP = 0.10; //10%
6 const int THRESHOLD = 7;//Use HIGH_MARKUP if not expected
7 //to sell in 7 days or less.
8 void introduction();
9 //Postcondition: Description of program is written on the screen.
10 void getInput(double& cost, int& turnover);
11 //Precondition: User is ready to enter values correctly.
12 //Postcondition: The value of cost has been set to the
13 //wholesale cost of one item. The value of turnover has been
14 //set to the expected number of days until the item is sold.
15 double price(double cost, int turnover);
16 //Precondition: cost is the wholesale cost of one item.
17 //turnover is the expected number of days until sale of the item.
18 //Returns the retail price of the item.
19 void giveOutput(double cost, int turnover, double price);
20 //Precondition: cost is the wholesale cost of one item; turnover is the
21 //expected time until sale of the item; price is the retail price of the item.
22 //Postcondition: The values of cost, turnover, and price have been
23 //written to the screen.
24 int main( )
25 {
26 double wholesaleCost, retailPrice;
27 int shelfTime;
28 introduction( );
29 getInput(wholesaleCost, shelfTime);
30 retailPrice = price(wholesaleCost, shelfTime);
31 giveOutput(wholesaleCost, shelfTime, retailPrice);
32 return 0;
33 }
34 //Uses iostream:
35 void introduction( )
36 {
37 using namespace std;
38 cout<< "This program determines the retail price for\n"
39 << "an item at a Quick-Shop supermarket store.\n";
40 }
41 //Uses iostream:
42 void getInput(double& cost, int& turnover)
43 {
44 using namespace std;
45 cout << "Enter the wholesale cost of item: $";
46 cin >> cost;
47 cout << "Enter the expected number of days until sold: ";
48 cin >> turnover;
49 }
50 //Uses iostream:
51 void giveOutput(double cost, int turnover, double price)
52 {
53 using namespace std;
54 cout.setf(ios::fixed);
55 cout.setf(ios::showpoint);
56 cout.precision(2);
57 cout << "Wholesale cost = $" << cost << endl
58 << "Expected time until sold = "
59 << turnover << " days" << endl
60 << "Retail price = $" << price << endl;
61 }
62 //Uses defined constants LOW_MARKUP, HIGH_MARKUP, and THRESHOLD:
63 double price(double cost, int turnover)
64 {
65 if (turnover <= THRESHOLD)
66 return ( cost + (LOW_MARKUP * cost) );
67 else
68 return ( cost + (HIGH_MARKUP * cost) );
69
70 }
Sample Dialogue
This program determines the retail price for an item at a Quick-Shop supermarket store. Enter the wholesale cost of item: $1.21 Enter the expected number of days until sold: 5 Wholesale cost = $1.21 Expected time until sold = 5 days Retail price = $1.27
An important technique in testing a program is to test all kinds of input. There is no precise definition of what we mean by a “kind” of input, but in practice, it is often easy to decide what kinds of input data a program deals with. In the case of our supermarket program, there are two main kinds of input: input that uses the low markup of 5 percent and input that uses the high markup of 10 percent. Thus, we should test at least one case in which the item is expected to remain on the shelf for less than 7 days and at least one case in which the item is expected to remain on the shelf for more than 7 days.
Another testing strategy is to test boundary values. Unfortunately, boundary value is another vague concept. An input (test) value is a boundary value if it is a value at which the program changes behavior. For example, in our supermarket program, the program’s behavior changes at an expected shelf stay of 7 days. Thus, 7 is a boundary value; the program behaves differently for a number of days that is less than or equal to 7 than it does for a number of days that is greater than 7. Hence, we should test the program on at least one case in which the item is expected to remain on the shelf for exactly 7 days. Normally, you should also test input that is one step away from the boundary value as well, since you can easily be off by one in deciding where the boundary is. Hence, we should test our program on input for an item that is expected to remain on the shelf for 6 days, an item that is expected to remain on the shelf for 7 days, and an item that is expected to remain on the shelf for 8 days. (This is in addition to the test inputs described in the previous paragraph, which should be well below and well above 7 days.)
Can a function definition appear inside the body of another function definition?
Can a function definition contain a call to another function?
Rewrite the function declaration comment for the function order shown in Display 5.8 so that it is expressed in terms of preconditions and postconditions.
Give a precondition and a postcondition for the predefined function sqrt
, which returns the square root of its argument.