To illustrate how algorithms are developed, we solve two variations of a problem that averages student grades. Consider the following problem statement:
A class of ten students took a quiz. The grades (integers in the range 0–100) for this quiz are available to you. Determine the class average on the quiz.
The class average is equal to the sum of the grades divided by the number of students. The algorithm for solving this problem on a computer must input each grade, keep track of the total of all grades entered, perform the averaging calculation and print the result.
Let’s use pseudocode to list the actions to execute and specify the order in which they should execute. We use counter-controlled iteration to input the grades one at a time. This technique uses a variable called a counter (or control variable) to control the number of times a set of statements will execute. Counter-controlled iteration is often called definite iteration, because the number of iterations is known before the loop begins executing. In this example, iteration terminates when the counter exceeds 10. This section presents a fully developed pseudocode algorithm (Fig. 4.9) and a corresponding C++ program (Fig. 4.10) that implements the algorithm. In Section 4.10, we demonstrate how to develop pseudocode algorithms from scratch.
Note the references in the algorithm of Fig. 4.9 to a total and a counter. A total is a variable used to accumulate the sum of several values. A counter is a variable used to count—in this case, the grade counter indicates which of the 10 grades is about to be entered by the user. Variables used to store totals normally are initialized to zero before being used in a program. In pseudocode, we do not use braces around the statements that form the body of the pseudocode While structure, but you could.
Experience has shown that the most difficult part of solving a problem on a computer is developing the algorithm for the solution. Once a correct algorithm has been specified, producing a working C++ program from it is usually straightforward.
In Fig. 4.10, the main
function implements the class-averaging algorithm described by the pseudocode in Fig. 4.9—it allows the user to enter 10 grades, then calculates and displays the average.
main
Lines 8, 9, 14 and 21 declare local variables total
, gradeCounter
, grade
and average
, respectively. Variable gradeCounter
is of type unsigned int
, because it can assume only the values from 1 through 11 (11 terminates the loop), which are all positive values. In general, counters that should store only nonnegative values should be declared with unsigned
types. Variables of unsigned
integer types can represent values from 0 to approximately twice the positive range of the corresponding signed integer types. You can determine your platform’s maximum unsigned int
value with the constant UINT_MAX
from <climits>
. The program’s other variables are of type int
. Variable grade
stores the user input.
A variable declared in a function body is a local variable and can be used only from the line of its declaration to the closing right brace of the block in which the variable is declared. A local variable’s declaration must appear before the variable is used; otherwise, a compilation error occurs. Variable grade
—declared in the body of the while
loop—can be used only in that block.
total
and gradeCounter
Lines 8–9 declare and initialize total
to 0
and gradeCounter
to 1
. These initializations occur before the variables are used in calculations.
Initialize each total and counter, either in its declaration or in an assignment statement. Totals are normally initialized to 0. Counters are normally initialized to 0 or 1, depending on how they’re used (we’ll show examples of when to use 0 and when to use 1).
Line 12 indicates that the while
statement should continue looping (also called iterating) as long as gradeCounter
’s value is less than or equal to 10. While this condition remains true, the while
statement repeatedly executes the statements between the braces that delimit its body (lines 12–18).
Line 13 displays the prompt "Enter grade: "
. Line 15 inputs the grade entered by the user and assigns it to variable grade
. Then line 16 adds the new grade
entered by the user to the total
and assigns the result to total
, replacing its previous value.
Line 17 adds 1
to gradeCounter
to indicate that the program has processed a grade and is ready to input the next grade from the user. Incrementing gradeCounter
eventually causes it to exceed 10. Then the loop terminates, because its condition (line 12) becomes false.
When the loop terminates, line 21 performs the averaging calculation in the average
variable’s initializer. Line 24 displays the text "Total of all 10 grades is "
followed by variable total
’s value. Then, line 25 displays the text "Class average is "
followed by variable average
’s value. When execution reaches line 26, the program terminates.
Notice that this example contains only a main
function that performs all the work. In this chapter and in Chapter 3, you’ve seen examples consisting of a class and a main
program that creates an object of the class and calls its member functions. When it does not make sense to try to create a reusable class to demonstrate a concept, we’ll place the program’s statements entirely within main
.
This example’s averaging calculation produces an integer result. The program’s output indicates that the sum of the grade values in the sample execution is 846, which, when divided by 10, should yield the floating-point number 84.6. However, the result of the calculation total / 10
(line 21 of Fig. 4.10) is the integer 84, because total
and 10
are both integers. Dividing two integers results in integer division—any fractional part of the calculation is truncated (i.e., lost). In the next section we’ll see how to obtain a floating-point result from the averaging calculation.
Assuming that integer division rounds (rather than truncates) can lead to incorrect results. For example, 7 ÷ 4, which yields 1.75 in conventional arithmetic, truncates to 1 in integer arithmetic, rather than rounding to 2.
In Fig. 4.10, line 16
total = total + grade; // add grade to total
adds each grade
entered by the user to the total
. Even this simple statement has a potential problem—adding the integers could result in a value that’s too large to store in an int
variable. This is known as arithmetic overflow and causes undefined behavior, which can lead to unintended results and security problems. See, for example,
http://en.wikipedia.org/wiki/Integer_overflow#Security_ramifications
Figure 2.5 had the same issue in line 19, which calculated the sum of two int
values entered by the user:
sum = number1 + number2; // add the numbers; store result in sum
The maximum and minimum values that can be stored in an int
variable are represented by the constants INT_MAX
and INT_MIN
, respectively, which are defined in the header <climits>
. There are similar constants for the other integral types and for floating-point types (header <cfloat>
). To see these constants’ values on your platform, include the appropriate header in a program that outputs the values with cout
, as in
cout << "INT_MAX = " << INT_MAX << "
";
For arithmetic calculations like those in line 16 of Fig. 4.10 and line 19 of Fig. 2.5, it’s considered a good practice to ensure before you perform them that they will not overflow. The code for doing this is shown at www.securecoding.cert.org
—just search for guideline “INT32-CPP.” The code uses the &&
(logical AND) and ||
(logical OR) operators, which are introduced in Chapter 5.
Any time a program receives input from the user, various problems might occur. For example, in line 15 of Fig. 4.10
cin >> grade; // input next grade
we assume that the user will enter an integer grade in the range 0 to 100. However, the user could enter an integer less than 0, an integer greater than 100, an integer outside the range of values that can be stored in an int
variable, a number containing a decimal point or a value containing letters or special symbols that’s not even an integer.
To ensure that inputs are valid, industrial-strength programs must test for all possible erroneous cases. A program that inputs grades should validate the grades by using range checking to ensure that they’re values from 0 to 100. You can then ask the user to reenter any value that’s out of range. If a program requires inputs from a specific set of values (e.g., nonsequential product codes), you can ensure that each input matches a value in the set.