7. Arrays and Vectors

Objectives

In this chapter you’ll learn:

Image  To use the array data structure to represent a set of related data items.

Image  To use arrays to store, sort and search lists and tables of values.

Image  To declare arrays, initialize arrays and refer to the individual elements of arrays.

Image  To pass arrays to functions.

Image  Basic searching and sorting techniques.

Image  To declare and manipulate multidimensional arrays.

Image  To use C++ Standard Library class template vector.

Now go, write it before them in a table, and note it in a book.
—Isaiah 30:8

Begin at the beginning, . . . and go on till you come to the end: then stop.
—Lewis Carroll

To go beyond is as wrong as to fall short.
—Confucius

Outline

7.1 Introduction

This chapter introduces the important topic of data structures—collections of related data items. Arrays are data structures consisting of related data items of the same type. We considered classes in Chapter 3. In Chapter 19, Bits, Characters, C Strings and structs, we discuss the notion of structures. Structures and classes can each hold related data items of possibly different types. Arrays, structures and classes are “static” entities in that they remain the same size throughout program execution. (They may, of course, be of automatic storage class and hence be created and destroyed each time the blocks in which they are defined are entered and exited.)

After discussing how arrays are declared, created and initialized, we present a series of practical examples that demonstrate several common array manipulations. We then explain how character strings (represented until now by string objects) can also be represented by character arrays. We present an example of searching arrays to find particular elements. The chapter also introduces one of the most important computing applications—sorting data (i.e., putting the data in some particular order). Two sections of the chapter enhance the case study of class GradeBook in Chapters 36. In particular, we use arrays to enable the class to maintain a set of grades in memory and analyze student grades from multiple exams in a semester—two capabilities that were absent from previous versions of the GradeBook class. These and other chapter examples demonstrate the ways in which arrays allow programmers to organize and manipulate data.

The style of arrays we use throughout most of this chapter are C-style pointer-based arrays. (We’ll study pointers in Chapter 8.) In the final section of this chapter, and in Chapter 20, Standard Template Library (STL), we’ll cover arrays as full-fledged objects called vectors. We’ll discover that these object-based arrays are safer and more versatile than the C-style, pointer-based arrays we discuss in the early part of this chapter.

7.2 Arrays

An array is a consecutive group of memory locations that all have the same type. To refer to a particular location or element in the array, we specify the name of the array and the position number of the particular element in the array.

Figure 7.1 shows an integer array called c. This array contains 12 elements. A program refers to any one of these elements by giving the name of the array followed by the position number of the particular element in square brackets ([]). The position number is more formally called a subscript or index (this number specifies the number of elements from the beginning of the array). The first element in every array has subscript 0 (zero) and is sometimes called the zeroth element. Thus, the elements of array c are c[0] (pronounced “c sub zero”), c[1], c[2] and so on. The highest subscript in array c is 11, which is 1 less than the number of elements in the array (12). Array names follow the same conventions as other variable names, i.e., they must be identifiers.

Fig. 7.1 Array of 12 elements.

Array of 12 elements.

A subscript must be an integer or integer expression (using any integral type). If a program uses an expression as a subscript, then the program evaluates the expression to determine the subscript. For example, if we assume that variable a is equal to 5 and that variable b is equal to 6, then the statement

c[ a + b ] += 2;


adds 2 to array element c[ 11 ]. Note that a subscripted array name is an lvalue—it can be used on the left side of an assignment, just as nonarray variable names can.

Let us examine array c in Fig. 7.1 more closely. The name of the entire array is c. Its 12 elements are referred to as c[0] to c[ 11 ]. The value of c[0] is -45, the value of c[1] is 6, the value of c[2] is 0, the value of c[7] is 62, and the value of c[ 11 ] is 78. To print the sum of the values contained in the first three elements of array c, we’d write

cout << c[ 0 ] + c[ 1 ] + c[ 2 ] << endl;


To divide the value of c[ 6 ] by 2 and assign the result to the variable x, we would write

x = c[ 6 ] / 2;


Common Programming Error 7.1

Common Programming Error 7.1

Note the difference between the “seventh element of the array” and “array element 7.” Array subscripts begin at 0, so the “seventh element of the array” has a subscript of 6, while “array element 7” has a subscript of 7 and is actually the eighth element of the array. Unfortunately, this distinction frequently is a source of off-by-one errors. To avoid such errors, we refer to specific array elements explicitly by their array name and subscript number (e.g., c[6] or c[7]).

The brackets used to enclose the subscript of an array are actually an operator. Brackets have the same level of precedence as parentheses. Figure 7.2 shows the precedence and associativity of the operators introduced so far. Note that brackets ([]) have been added to the first row of Fig. 7.2. The operators are shown top to bottom in decreasing order of precedence with their associativity and type.

Fig. 7.2 Operator precedence and associativity.

Operators

Associativity

Type

::

left to right

scope resolution

()

[]

left to right

highest

++

--

static_cast<type>(operand)

left to right

unary (postfix)

++

--

+

-

!

right to left

unary (prefix)

*

/

%

left to right

multiplicative

+

-

left to right

additive

<<

>>

left to right

insertion/extraction

<

<=

>

>=

left to right

relational

==

!=

left to right

equality

&&

left to right

logical AND

||

left to right

logical OR

?:

right to left

conditional

=

+=

-=

*=

/=

%=

right to left

assignment

,

left to right

comma

7.3 Declaring Arrays

Arrays occupy space in memory. To specify the type of the elements and the number of elements required by an array use a declaration of the form:

type arrayNamearraySize ];


The compiler reserves the appropriate amount of memory. (Recall that a declaration which reserves memory is more properly known as a definition in C++.) The arraySize must be an integer constant greater than zero. For example, to tell the compiler to reserve 12 elements for integer array c, use the declaration

int c[ 12 ]; // c is an array of 12 integers


Memory can be reserved for several arrays with a single declaration. The following declaration reserves 100 elements for the integer array b and 27 elements for the integer array x.

int b[ 100 ], // b is an array of 100 integers
    x[ 27 ]; // x is an array of 27 integers


Good Programming Practice 7.1

Good Programming Practice 7.1

We declare one array per declaration for readability, modifiability and ease of commenting.

Arrays can be declared to contain values of any nonreference data type. For example, an array of type char can be used to store a character string. Until now, we have used string objects to store character strings. Section 7.4 introduces using character arrays to store strings. Character strings and their similarity to arrays (a relationship C++ inherited from C), and the relationship between pointers and arrays, are discussed in Chapter 8.

7.4 Examples Using Arrays

This section presents many examples that demonstrate how to declare arrays, how to initialize arrays and how to perform common array manipulations.

7.4.1 Declaring an Array and Using a Loop to Initialize the Array’s Elements

The program in Fig. 7.3 declares 10-element integer array n (line 12). Lines 15–16 use a for statement to initialize the array elements to zeros. Like other automatic variables, automatic arrays are not implicitly initialized to zero although static arrays are. The first output statement (line 18) displays the column headings for the columns printed in the subsequent for statement (lines 21–22), which prints the array in tabular format. Remember that setw specifies the field width in which only the next value is to be output.

Fig. 7.3 Initializing an array’s elements to zeros and printing the array.

 1   // Fig. 7.3: fig07_03.cpp
 2   // Initializing an array.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;

Image

7.4.2 Initializing an Array in a Declaration with an Initializer List

The elements of an array also can be initialized in the array declaration by following the array name with an equals sign and a brace-delimited comma-separated list of initializers. The program in Fig. 7.4 uses an initializer list to initialize an integer array with 10 values (line 13) and prints the array in tabular format (lines 15–19).

Fig. 7.4 Initializing the elements of an array in its declaration.

 1   // Fig. 7.4: fig07_04.cpp
 2   // Initializing an array in a declaration.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include <iomanip>
 8   using std::setw;
 9

Image

If there are fewer initializers than elements in the array, the remaining array elements are initialized to zero. For example, the elements of array n in Fig. 7.3 could have been initialized to zero with the declaration

int n[ 10 ] = {}; // initialize elements of array n to 0


The declaration implicitly initializes the elements to zero, because there are fewer initializers (none in this case) than elements in the array. This technique can be used only in the array’s declaration, whereas the initialization technique shown in Fig. 7.3 can be used repeatedly during program execution to “reinitialize” an array’s elements.

If the array size is omitted from a declaration with an initializer list, the compiler determines the number of elements in the array by counting the number of elements in the initializer list. For example,

int n[] = { 12345 };


creates a five-element array.

If the array size and an initializer list are specified in an array declaration, the number of initializers must be less than or equal to the array size. The array declaration

int n[ 5 ] = { 322764189514 };


causes a compilation error, because there are six initializers and only five array elements.

Common Programming Error 7.2

Common Programming Error 7.2

Providing more initializers in an array initializer list than there are elements in the array is a compilation error.

Common Programming Error 7.3

Common Programming Error 7.3

Forgetting to initialize the elements of an array whose elements should be initialized is a logic error.

7.4.3 Specifying an Array’s Size with a Constant Variable and Setting Array Elements with Calculations

Figure 7.5 sets the elements of a 10-element array s to the even integers 2, 4, 6, . . ., 20 (lines 17–18) and prints the array in tabular format (lines 20–24). These numbers are generated (line 18) by multiplying each successive value of the loop counter by 2 and adding 2.

Fig. 7.5 Generating values to be placed into elements of an array.

 1   // Fig. 7.5: fig07_05.cpp
 2   // Set array s to the even integers from 2 to 20.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include <iomanip>
 8   using std::setw;
 9
10   int main()
11   {
12      // constant variable can be used to specify array size
13      const int arraySize = 10;
14
15      int s[ arraySize ]; // array s has 10 elements
16
17      for ( int i = 0; i < arraySize; i++ ) // set the values
18         s[ i ] = 2 + 2 * i;                                 
19
20      cout << "Element" << setw( 13 ) << "Value" << endl;
21
22      // output contents of array s in tabular format
23      for ( int j = 0; j < arraySize; j++ )
24         cout << setw( 7 ) << j << setw( 13 ) << s[ j ] << endl;
25
26      return 0// indicates successful termination
27   } // end main


Image

Line 13 uses the const qualifier to declare a so-called constant variable arraySize with the value 10. Constant variables must be initialized with a constant expression when they are declared and cannot be modified thereafter (as shown in Fig. 7.6 and Fig. 7.7). Constant variables are also called named constants or read-only variables.

Fig. 7.6 Initializing and using a constant variable.

 1   // Fig. 7.6: fig07_06.cpp
 2   // Using a properly initialized constant variable.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   int main()
 8   {
 9      const int x = 7// initialized constant variable
10
11      cout << "The value of constant variable x is: " << x << endl;
12
13      return 0// indicates successful termination
14   } // end main


The value of constant variable x is: 7


Fig. 7.7 const variables must be initialized.

 1   // Fig. 7.7: fig07_07.cpp
 2   // A const variable must be initialized.
 3
 4   int main()
 5   {
 6      const int x; // Error: x must be initialized
 7
 8      x = 7; // Error: cannot modify a const variable
 9
10      return 0// indicates successful termination
11   } // end main


Borland C++ command-line compiler error message:

Error E2304 fig07_07.cpp 6: Constant variable 'x' must be initialized
   in function main()
Error E2024 fig07_07.cpp 8: Cannot modify a const object in function main()


Microsoft Visual C++ compiler error message:

C:cppfp_examplesch07fig07_07.cpp(6) : error C2734: 'x' : const object
   must be initialized if not extern
C:cppfp_examplesch07fig07_07.cpp(8) : error C3892: 'x' : you cannot
   assign to a variable that is const


GNU C++ compiler error message:

fig07_07.cpp:6: error: uninitialized const 'x'
fig07_07.cpp:8: error: assignment of read-only variable 'x'


Common Programming Error 7.4

Common Programming Error 7.4

Not assigning a value to a constant variable when it is declared is a compilation error.

Common Programming Error 7.5

Common Programming Error 7.5

Assigning a value to a constant variable in an executable statement is a compilation error.

In Fig. 7.7, note that the compilation errors produced by Borland C++ and Microsoft Visual C++ refer to the int variable x as a "const object." The ISO/IEC C++ standard defines an “object” as any “region of storage.” Like objects of classes, fundamental-type variables also occupy space in memory, so they are often referred to as “objects.”

Constant variables can be placed anywhere a constant expression is expected. In Fig. 7.5, constant variable arraySize specifies the size of array s in line 15.

Common Programming Error 7.6

Common Programming Error 7.6

Only constants can be used to declare the size of automatic and static arrays. Not using a constant for this purpose is a compilation error.

Using constant variables to specify array sizes makes programs more scalable. In Fig. 7.5, the first for statement could fill a 1000-element array by simply changing the value of arraySize in its declaration from 10 to 1000. If the constant variable arraySize had not been used, we would have to change lines 15, 17 and 23 of the program to scale the program to handle 1000 array elements. As programs get larger, this technique becomes more useful for writing clearer, easier-to-modify programs.

Software Engineering Observation 7.1

Software Engineering Observation 7.1

Defining the size of each array as a constant variable instead of a literal constant can make programs more scalable.

Good Programming Practice 7.2

Good Programming Practice 7.2

Defining the size of an array as a constant variable instead of a literal constant makes programs clearer. This technique eliminates so-called magic numbers. For example, repeatedly mentioning the size 10 in array-processing code for a 10-element array gives the number 10 an artificial significance and can be confusing when the program includes other 10s that have nothing to do with the array size.

7.4.4 Summing the Elements of an Array

Often, the elements of an array represent a series of values to be used in a calculation. For example, if the elements of an array represent exam grades, a professor may wish to total the elements of the array and use that sum to calculate the class average for the exam. The examples using class GradeBook later in the chapter, namely Figs. 7.167.17 and Figs. 7.237.24, use this technique.

The program in Fig. 7.8 sums the values contained in the 10-element integer array a. The program declares, creates and initializes the array in line 10. The for statement (lines 14–15) performs the calculations. The values being supplied as initializers for array a also could be read into the program from the user at the keyboard, or from a file on disk (see Chapter 17, File Processing). For example, the for statement

Fig. 7.8 Computing the sum of the elements of an array.

 1   // Fig. 7.8: fig07_08.cpp
 2   // Compute the sum of the elements of the array.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   int main()
 8   {
 9      const int arraySize = 10; // constant variable indicating size of array
10      int a[ arraySize ] = { 876894100837885917687 };
11      int total = 0;
12
13      // sum contents of array a           
14      for ( int i = 0; i < arraySize; i++ )
15         total += a[ i ];                  
16
17      cout << "Total of array elements: " << total << endl;
18
19      return 0// indicates successful termination
20   } // end main


Total of array elements: 849


for ( int j = 0; j < arraySize; j++ )
   cin >> a[ j ];


reads one value at a time from the keyboard and stores the value in element a[ j ].

7.4.5 Using Bar Charts to Display Array Data Graphically

Many programs present data to users in a graphical manner. For example, numeric values are often displayed as bars in a bar chart. In such a chart, longer bars represent proportionally larger numeric values. One simple way to display numeric data graphically is with a bar chart that shows each numeric value as a bar of asterisks (*).

Professors often like to examine the distribution of grades on an exam. A professor might graph the number of grades in each of several categories to visualize the grade distribution. Suppose the grades were 87, 68, 94, 100, 83, 78, 85, 91, 76 and 87. Note that there was one grade of 100, two grades in the 90s, four grades in the 80s, two grades in the 70s, one grade in the 60s and no grades below 60. Our next program (Fig. 7.9) stores this grade distribution data in an array of 11 elements, each corresponding to a category of grades. For example, n[0] indicates the number of grades in the range 0–9, n[7] indicates the number of grades in the range 70–79 and n[ 10 ] indicates the number of grades of 100. The two versions of class GradeBook later in the chapter (Figs. 7.167.17 and Figs. 7.237.24) contain code that calculates these grade frequencies based on a set of grades. For now, we manually create the array by looking at the set of grades.

Fig. 7.9 Bar chart printing program.

 1   // Fig. 7.9: fig07_09.cpp
 2   // Bar chart printing program.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include <iomanip>
 8   using std::setw;
 9
10   int main()
11   {
12      const int arraySize = 11;
13      int n[ arraySize ] = { 00000012421 };
14
15      cout << "Grade distribution:" << endl;
16
17      // for each element of array n, output a bar of the chart
18      for ( int i = 0; i < arraySize; i++ )
19      {
20         // output bar labels ("0-9:", ..., "90-99:", "100:" )
21         if ( i == 0 )
22            cout << "  0-9: ";
23         else if ( i == 10 )
24            cout << "  100: ";
25         else
26            cout << i * 10 << "-" << ( i * 10 ) + 9 << ": ";
27
28         // print bar of asterisks
29         for ( int stars = 0; stars < n[ i ]; stars++ )
30            cout << '*';
31
32         cout << endl; // start a new line of output
33      } // end outer for
34
35      return 0// indicates successful termination
36   } // end main


Grade distribution:
  0-9:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
  100: *


The program reads the numbers from the array and graphs the information as a bar chart, displaying each grade range followed by a bar of asterisks indicating the number of grades in that range. To label each bar, lines 21–26 output a grade range (e.g., "70-79: ") based on the current value of counter variable i. The nested for statement (lines 29–30) outputs the bars. Note the loop-continuation condition in line 29 (stars < n[ i ]). Each time the program reaches the inner for, the loop counts from 0 up to n[ i ], thus using a value in array n to determine the number of asterisks to display. In this example, n[ 0 ]n[5] contain zeros because no students received a grade below 60. Thus, the program displays no asterisks next to the first six grade ranges.

Common Programming Error 7.7

Common Programming Error 7.7

Although it is possible to use the same control variable in a for statement and in a second for statement nested inside, this is confusing and can lead to logic errors.

7.4.6 Using the Elements of an Array as Counters

Sometimes, programs use counter variables to summarize data, such as the results of a survey. In Fig. 6.8, we used separate counters in our die-rolling program to track the number of occurrences of each side of a die as the program rolled the die 6,000,000 times. An array version of this program is shown in Fig. 7.10.

Fig. 7.10 Die-rolling program using an array instead of switch.

 1   // Fig. 7.10: fig07_10.cpp
 2   // Roll a six-sided die 6,000,000 times.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include <iomanip>
 8   using std::setw;
 9
10   #include <cstdlib>
11   using std::rand;
12   using std::srand;
13
14   #include <ctime>

15   using std::time;
16
17   int main()
18   {
19      const int arraySize = 7// ignore element zero
20      int frequency[ arraySize ] = {}; // initialize elements to 0
21
22      srand( time( 0 ) ); // seed random number generator
23
24      // roll die 6,000,000 times; use die value as frequency index
25      for ( int roll = 1; roll <= 6000000; roll++ )
26         frequency[ 1 + rand() % 6 ]++;
27
28      cout << "Face" << setw( 13 ) << "Frequency" << endl;
29
30      // output each array element's value
31      for ( int face = 1; face < arraySize; face++ )
32         cout << setw( 4 ) << face << setw( 13 ) << frequency[ face ]
33            << endl;
34
35      return 0// indicates successful termination
36   } // end main


Face    Frequency
   1      1000167
   2      1000149
   3      1000152
   4       998748
   5       999626
   6      1001158


Figure 7.10 uses the array frequency (line 20) to count the occurrences of each side of the die. The single statement in line 26 of this program replaces the switch statement in lines 30–52 of Fig. 6.8. Line 26 uses a random value to determine which frequency element to increment during each iteration of the loop. The calculation in line 26 produces a random subscript from 1 to 6, so array frequency must be large enough to store six counters. However, we use a seven-element array in which we ignore frequency[ 0 ]—it is more logical to have the die face value 1 increment frequency[ 1 ] than frequency[ 0 ]. Thus, each face value is used as a subscript for array frequency. We also replace lines 56–61 of Fig. 6.8 by looping through array frequency to output the results (lines 31–33).

7.4.7 Using Arrays to Summarize Survey Results

Our next example (Fig. 7.11) uses arrays to summarize the results of data collected in a survey. Consider the following problem statement:

Forty students were asked to rate the quality of the food in the student cafeteria on a scale of 1 to 10 (1 meaning awful and 10 meaning excellent). Place the 40 responses in an integer array and summarize the results of the poll.

Fig. 7.11 Poll analysis program.

 1   // Fig. 7.11: fig07_11.cpp
 2   // Student poll program.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include <iomanip>
 8   using std::setw;
 9
10   int main()
11   {
12      // define array sizes
13      const int responseSize = 40; // size of array responses
14      const int frequencySize = 11; // size of array frequency
15
16      // place survey responses in array responses
17      const int responses[ responseSize ] = { 1, 264859, 78,
18         101638, 610382, 76576867,
19         56656756486810 };
20
21      // initialize frequency counters to 0
22      int frequency[ frequencySize ] = {};
23
24      // for each answer, select responses element and use that value
25      // as frequency subscript to determine element to increment
26      for ( int answer = 0; answer < responseSize; answer++ )
27         frequency[ responses[ answer ] ]++;
28
29      cout << "Rating" << setw( 17 ) << "Frequency" << endl;
30
31      // output each array element's value
32      for ( int rating = 1; rating < frequencySize; rating++ )
33         cout << setw( 6 ) << rating << setw( 17 ) << frequency[ rating ]
34            << endl;
35
36      return 0// indicates successful termination
37   } // end main


Rating        Frequency
     1                2
     2                2
     3                2
     4                2
     5                5
     6               11
     7                5
     8                7
     9                1
    10                3


This is a typical array-processing application. We wish to summarize the number of responses of each type (i.e., 1 through 10). The array responses (lines 17–19) is a 40-element integer array of the students’ responses to the survey. Note that array responses is declared const, as its values do not (and should not) change. We use an 11-element array frequency (line 22) to count the number of occurrences of each response. Each element of the array is used as a counter for one of the survey responses and is initialized to zero. As in Fig. 7.10, we ignore frequency[ 0 ].

Software Engineering Observation 7.2

Software Engineering Observation 7.2

The const qualifier should be used to enforce the principle of least privilege. Using the principle of least privilege to properly design software can greatly reduce debugging time and improper side effects and can make a program easier to modify and maintain.

The first for statement (lines 26–27) takes the responses one at a time from the array responses and increments one of the 10 counters in the frequency array (frequency[ 1 ] to frequency[ 10 ]). The key statement in the loop is line 27, which increments the appropriate frequency counter, depending on the value of responses[ answer ].

Let’s consider several iterations of the for loop. When control variable answer is 0, the value of responses[ answer ] is the value of responses[ 0 ] (i.e., 1 in line 17), so the program interprets frequency[ responses[ answer ] ]++ as

frequency[ 1 ]++


which increments the value in array element 1. To evaluate the expression, start with the value in the innermost set of square brackets (answer). Once you know answer’s value (which is the value of the loop control variable in line 26), plug it into the expression and evaluate the next outer set of square brackets (i.e., responses[ answer ], which is a value selected from the responses array in lines 17–19). Then use the resulting value as the subscript for the frequency array to specify which counter to increment.

When answer is 1, responses[ answer ] is the value of responses[ 1 ], which is 2, so the program interprets frequency[ responses[ answer ] ]++ as

frequency[ 2 ]++


which increments array element 2.

When answer is 2, responses[ answer ] is the value of responses[ 2 ], which is 6, so the program interprets frequency[ responses[ answer ] ]++ as

frequency[ 6 ]++


which increments array element 6, and so on. Regardless of the number of responses processed in the survey, the program requires only an 11-element array (ignoring element zero) to summarize the results, because all the response values are between 1 and 10 and the subscript values for an 11-element array are 0 through 10.

If the data in the responses array had contained an invalid value, such as 13, the program would have attempted to add 1 to frequency[ 13 ], which is outside the bounds of the array. C++ has no array bounds checking to prevent the computer from referring to an element that does not exist. Thus, an executing program can “walk off” either end of an array without warning. You should ensure that all array references remain within the bounds of the array.

Common Programming Error 7.8

Common Programming Error 7.8

Referring to an element outside the array bounds is an execution-time logic error. It is not a syntax error.

Error-Prevention Tip 7.1

Error-Prevention Tip 7.1

When looping through an array, the array subscript should never go below 0 and should always be less than the total number of elements in the array (one less than the size of the array). Make sure that the loop-termination condition prevents accessing elements outside this range.

Portability Tip 7.1

Portability Tip 7.1

The (normally serious) effects of referencing elements outside the array bounds are system dependent. Often this results in changes to the value of an unrelated variable or a fatal error that terminates program execution.

C++ is an extensible language. Section 7.11 presents C++ Standard Library class template vector, which enables programmers to perform many operations that are not available for C++’s built-in arrays. For example, we’ll be able to compare vectors directly and assign one vector to another. In Chapter 11, we extend C++ further by implementing an array as a user-defined class of our own. This new array definition will enable us to input and output entire arrays with cin and cout, initialize arrays when they are created, prevent access to out-of-range array elements and change the range of subscripts (and even their subscript type) so that the first element of an array is not required to be element 0. We’ll even be able to use noninteger subscripts.

Error-Prevention Tip 7.2

Error-Prevention Tip 7.2

In Chapter 11, we’ll see how to develop a class representing a “smart array,” which checks that all subscript references are in bounds at runtime. Using such smart data types helps eliminate bugs.

7.4.8 Using Character Arrays to Store and Manipulate Strings

To this point, we have discussed only integer arrays. However, arrays may be of any type. We now introduce storing character strings in character arrays. Recall that, starting in Chapter 3, we have been using string objects to store character strings, such as the course name in our GradeBook class. A string such as "hello" is actually an array of characters. While string objects are convenient to use and reduce the potential for errors, character arrays that represent strings have several unique features, which we discuss in this section. As you continue your study of C++, you may encounter C++ capabilities that require you to use character arrays in preference to string objects. You may also be asked to update existing code using character arrays.

A character array can be initialized using a string literal. For example, the declaration

char string1[] = "first";


initializes the elements of array string1 to the individual characters in the string literal "first". The size of array string1 in the preceding declaration is determined by the compiler based on the length of the string. It is important to note that the string "first" contains five characters plus a special string-termination character called the null character. Thus, array string1 actually contains six elements. The character-constant that represents the null character is '' (backslash followed by zero). All strings represented by character arrays end with this character. A character array representing a string should always be declared large enough to hold the number of characters in the string and the terminating null character.

Character arrays also can be initialized with individual character constants in an initializer list. The preceding declaration is equivalent to the more tedious form

char string1[] = { 'f''i''r''s''t''' };


Note the use of single quotes to delineate each character constant. Also, note that we explicitly provided the terminating null character as the last initializer value. Without it, this array would simply represent an array of characters, not a string. As we discuss in Chapter 8, not providing a terminating null character for a string can cause logic errors.

Because a string is an array of characters, we can access individual characters in a string directly with array subscript notation. For example, string1[ 0 ] is the character 'f', string1[ 3 ] is the character 's' and string1[ 5 ] is the null character.

We can input a string directly into a character array from the keyboard using cin and >>. For example, the declaration

char string2[ 20 ];


creates a character array capable of storing a string of up to 19 characters and a terminating null character. The statement

cin >> string2;


reads a string from the keyboard into string2 and appends the null character to the end of the string input by the user. Note that the preceding statement provides only the name of the array and no information about the size of the array. It is your responsibility to ensure that the array into which the string is read is capable of holding any string the user types at the keyboard. By default, cin reads characters from the keyboard until the first white-space character is encountered—regardless of the array size. Thus, inputting data with cin and >> can insert data beyond the end of the array (see Section 8.13 for information on preventing insertion beyond the end of a char array).

Common Programming Error 7.9

Common Programming Error 7.9

Not providing cin >> with a character array large enough to store a string typed at the keyboard can result in loss of data in a program and other serious runtime errors.

A character array representing a null-terminated string can be output with cout and <<. The statement

cout << string2;


prints the array string2. Note that cout <<, like cin >>, does not care how large the character array is. The characters of the string are output until a terminating null character is encountered; the null character is not printed. [Note: cin and cout assume that character arrays should be processed as strings terminated by null characters; cin and cout do not provide similar input and output processing capabilities for other array types.]

Figure 7.12 demonstrates initializing a character array with a string literal, reading a string into a character array, printing a character array as a string and accessing individual characters of a string.

Fig. 7.12 Character arrays processed as strings.

 1   // Fig. 7.12: fig07_12.cpp
 2   // Treating character arrays as strings.
 3   #include <iostream>
 4   using std::cout;
 5   using std::cin;
 6   using std::endl;


 7
 8   int main()
 9   {
10      char string1[ 20 ]; // reserves 20 characters
11      char string2[] = "string literal"// reserves 15 characters
12
13      // read string from user into array string1
14      cout << "Enter the string "hello there": ";
15      cin >> string1; // reads "hello" [space terminates input]
16
17      // output strings
18      cout << "string1 is: " << string1 << " string2 is: " << string2;
19
20      cout << " string1 with spaces between characters is: ";
21
22      // output characters until null character is reached
23      for ( int i = 0; string1[ i ] != ''; i++ )
24         cout << string1[ i ] << ' ';             
25
26      cin >> string1; // reads "there"
27      cout << " string1 is: " << string1 << endl;
28
29      return 0// indicates successful termination
30   } // end main


Enter the string "hello there": hello there
string1 is: hello
string2 is: string literal
string1 with spaces between characters is:
h e l l o
string1 is: there


Lines 23–24 of Fig. 7.12 use a for statement to loop through the string1 array and print its characters separated by spaces. The condition in the for statement, string1[ i ] != '', is true until the loop encounters the terminating null character of the string.

7.4.9 Static Local Arrays and Automatic Local Arrays

Chapter 6 discussed the storage-class specifier static. A static local variable in a function definition exists for the program’s duration but is visible only in the function’s body.

Performance Tip 7.1

Performance Tip 7.1

We can apply static to a local array declaration so that the array is not created and initialized each time the program calls the function and is not destroyed each time the function terminates in the program. This can improve performance, especially when using large arrays.

A program initializes static local arrays when their declarations are first encountered. If a static array is not initialized explicitly by you, each element of that array is initialized to zero by the compiler when the array is created. Recall that C++ does not perform such default initialization for automatic variables.

Figure 7.13 demonstrates function staticArrayInit (lines 25–41) with a static local array (line 28) and function automaticArrayInit (lines 44–60) with an automatic local array (line 47).

Fig. 7.13 static array initialization and automatic array initialization.

 1   // Fig. 7.13: fig07_13.cpp
 2   // Static arrays are initialized to zero.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   void staticArrayInit( void ); // function prototype
 8   void automaticArrayInit( void ); // function prototype
 9
10   int main()
11   {
12      cout << "First call to each function: ";
13      staticArrayInit();
14      automaticArrayInit();
15
16      cout << " Second call to each function: ";
17      staticArrayInit();
18      automaticArrayInit();
19      cout << endl;
20
21      return 0// indicates successful termination
22   } // end main
23
24   // function to demonstrate a static local array
25   void staticArrayInit( void )
26   {
27      // initializes elements to 0 first time function is called
28      static int array1[ 3 ]; // static local array             
29
30      cout << " Values on entering staticArrayInit: ";
31
32      // output contents of array1
33      for ( int i = 0; i < 3; i++ )
34         cout << "array1[" << i << "] = " << array1[ i ] << "  ";
35
36      cout << " Values on exiting staticArrayInit: ";
37
38      // modify and output contents of array1
39      for ( int j = 0; j < 3; j++ )
40         cout << "array1[" << j << "] = " << ( array1[ j ] += 5 ) << "  ";
41   } // end function staticArrayInit
42
43   // function to demonstrate an automatic local array
44   void automaticArrayInit( void )
45   {
46      // initializes elements each time function is called   
47      int array2[ 3 ] = { 123 }; // automatic local array
48
49      cout << " Values on entering automaticArrayInit: ";
50
51      // output contents of array2
52      for ( int i = 0; i < 3; i++ )
53         cout << "array2[" << i << "] = " << array2[ i ] << "  ";


54
55      cout << " Values on exiting automaticArrayInit: ";
56
57      // modify and output contents of array2
58      for ( int j = 0; j < 3; j++ )
59         cout << "array2[" << j << "] = " << ( array2[ j ] += 5 ) << "  ";
60   } // end function automaticArrayInit


First call to each function:

Values on entering staticArrayInit:
array1[0] = 0  array1[1] = 0  array1[2] = 0
Values on exiting staticArrayInit:
array1[0] = 5  array1[1] = 5  array1[2] = 5

Values on entering automaticArrayInit:
array2[0] = 1  array2[1] = 2  array2[2] = 3
Values on exiting automaticArrayInit:
array2[0] = 6  array2[1] = 7  array2[2] = 8

Second call to each function:

Values on entering staticArrayInit:
array1[0] = 5  array1[1] = 5  array1[2] = 5
Values on exiting staticArrayInit:
array1[0] = 10  array1[1] = 10  array1[2] = 10

Values on entering automaticArrayInit:
array2[0] = 1  array2[1] = 2  array2[2] = 3
Values on exiting automaticArrayInit:
array2[0] = 6  array2[1] = 7  array2[2] = 8


Function staticArrayInit is called twice (lines 13 and 17). The static local array is initialized to zero by the compiler the first time the function is called. The function prints the array, adds 5 to each element and prints the array again. The second time the function is called, the static array contains the modified values stored during the first function call. Function automaticArrayInit also is called twice (lines 14 and 18). The elements of the automatic local array are initialized (line 47) with the values 1, 2 and 3. The function prints the array, adds 5 to each element and prints the array again. The second time the function is called, the array elements are reinitialized to 1, 2 and 3. The array has automatic storage class, so the array is recreated and reinitialized during each call to automaticArrayInit.

Common Programming Error 7.10

Common Programming Error 7.10

Assuming that elements of a function’s local static array are initialized every time the function is called can lead to logic errors in a program.

7.5 Passing Arrays to Functions

To pass an array argument to a function, specify the name of the array without any brackets. For example, if array hourlyTemperatures has been declared as

int hourlyTemperatures[ 24 ];


the function call

modifyArray( hourlyTemperatures, 24 );


passes array hourlyTemperatures and its size to function modifyArray. When passing an array to a function, the array size is normally passed as well, so the function can process the specific number of elements in the array. Otherwise, we would need to build this knowledge into the called function itself or, worse yet, place the array size in a global variable. In Section 7.11, when we present C++ Standard Library class template vector to represent a more robust type of array, you’ll see that the size of a vector is built in—every vector object “knows” its own size, which can be obtained by invoking the vector object’s size member function. Thus, when we pass a vector object into a function, we will not have to pass the size of the vector as an argument.

C++ passes arrays to functions by reference—the called functions can modify the element values in the callers’ original arrays. The value of the name of the array is the address in the computer’s memory of the first element of the array. Because the starting address of the array is passed, the called function knows precisely where the array is stored in memory. Therefore, when the called function modifies array elements in its function body, it is modifying the actual elements of the array in their original memory locations.

Performance Tip 7.2

Performance Tip 7.2

Passing arrays by reference makes sense for performance reasons. If arrays were passed by value, a copy of each element would be passed. For large, frequently passed arrays, this would be time consuming and would require considerable storage for the copies of the array elements.

Software Engineering Observation 7.3

Software Engineering Observation 7.3

It is possible to pass an array by value (by using a simple trick we explain in Chapter 19)—however, this is rarely done.

Although entire arrays are passed by reference, individual array elements are passed by value exactly as simple variables are. Such simple single pieces of data are called scalars or scalar quantities. To pass an element of an array to a function, use the subscripted name of the array element as an argument in the function call. In Chapter 6, we showed how to pass scalars (i.e., individual variables and array elements) by reference with references. In Chapter 8, we show how to pass scalars by reference with pointers.

For a function to receive an array through a function call, the function’s parameter list must specify that the function expects to receive an array. For example, the function header for function modifyArray might be written as

void modifyArray( int b[], int arraySize )


indicating that modifyArray expects to receive the address of an array of integers in parameter b and the number of array elements in parameter arraySize. The array’s size is not required in the array brackets. If it is included, the compiler ignores it; thus, arrays of any size can be passed to the function. C++ passes arrays to functions by reference—when the called function uses the array name b, it refers to the actual array in the caller (i.e., array hourlyTemperatures discussed at the beginning of this section).

Note the strange appearance of the function prototype for modifyArray

void modifyArray( int [], int );


This prototype could have been written

void modifyArray( int anyArrayName[], int anyVariableName );


but, as we saw in Chapter 3, C++ compilers ignore variable names in prototypes. Remember, the prototype tells the compiler the number of arguments and the type of each argument (in the order in which the arguments are expected to appear).

The program in Fig. 7.14 demonstrates the difference between passing an entire array and passing an array element. Lines 22–23 print the five original elements of integer array a. Line 28 passes a and its size to function modifyArray (lines 45–50), which multiplies each of a’s elements by 2 (through parameter b). Then, lines 32–33 print array a again in main. As the output shows, the elements of a are indeed modified by modifyArray. Next, line 36 prints the value of scalar a[3], then line 38 passes element a[ 3 ] to function modifyElement (lines 54–58), which multiplies its parameter by 2 and prints the new value. Note that when line 39 again prints a[3] in main, the value has not been modified, because individual array elements are passed by value.

Fig. 7.14 Passing arrays and individual array elements to functions.

 1  // Fig. 7.14: fig07_14.cpp
 2   // Passing arrays and individual array elements to functions.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include <iomanip>
 8   using std::setw;
 9
10   void modifyArray( int [], int ); // appears strange; array and size
11   void modifyElement( int ); // receive array element value          
12
13   int main()
14   {
15      const int arraySize = 5// size of array a
16      int a[ arraySize ] = { 01234 }; // initialize array a
17
18      cout << "Effects of passing entire array by reference:"
19         << " The values of the original array are: ";
20
21      // output original array elements
22      for ( int i = 0; i < arraySize; i++ )
23         cout << setw( 3 ) << a[ i ];
24
25      cout << endl;
26
27      // pass array a to modifyArray by reference
28      modifyArray( a, arraySize );               
29      cout << "The values of the modified array are: ";
30
31      // output modified array elements
32      for ( int j = 0; j < arraySize; j++ )
33         cout << setw( 3 ) << a[ j ];
34
35      cout << " Effects of passing array element by value:"
36         << " a[3] before modifyElement: " << a[ 3 ] << endl;
37
38      modifyElement( a[ 3 ] ); // pass array element a[ 3 ] by value
39      cout << "a[3] after modifyElement: " << a[ 3 ] << endl;
40
41      return 0// indicates successful termination
42   } // end main
43
44   // in function modifyArray, "b" points to the original array "a" in memory
45   void modifyArray( int b[], int sizeOfArray )                              
46   {                                                                         
47      // multiply each array element by 2                                    
48      for ( int k = 0; k < sizeOfArray; k++ )                                
49         b[ k ] *= 2;                                                        
50   // end function modifyArray                                             
51
52   // in function modifyElement, "e" is a local copy of                   
53   // array element a[ 3 ] passed from main                               
54   void modifyElement( int e )                                            
55   {                                                                      
56      // multiply parameter by 2                                          
57      cout << "Value of element in modifyElement: " << ( e *= 2 ) << endl;
58   // end function modifyElement                                        


Effects of passing entire array by reference:

The values of the original array are:
  0  1  2  3  4
The values of the modified array are:
  0  2  4  6  8

Effects of passing array element by value:

a[3] before modifyElement: 6
Value of element in modifyElement: 12
a[3] after modifyElement: 6


There may be situations in your programs in which a function should not be allowed to modify array elements. C++ provides the type qualifier const that can be used to prevent modification of array values in the caller by code in a called function. When a function specifies an array parameter that is preceded by the const qualifier, the elements of the array become constant in the function body, and any attempt to modify an element of the array in the function body results in a compilation error. This enables you to prevent accidental modification of array elements in the function’s body.

Figure 7.15 demonstrates the const qualifier. Function tryToModifyArray (lines 21–26) is defined with parameter const int b[], which specifies that array b is constant and cannot be modified. Each of the three attempts by the function to modify array b’s elements (lines 23–25) results in a compilation error. The Borland C++ compiler, for example, produces the error “Cannot modify a const object.” [Note: The C++ standard defines an “object” as any “region of storage,” thus including variables or array elements of fundamental data types as well as instances of classes (what we’ve been calling objects).] This message indicates that using a const object (e.g., b[0]) as an lvalue is an error—you cannot assign a new value to a const object by placing it on the left of an assignment operator. Note that compiler error messages vary between compilers (as shown in Fig. 7.15). The const qualifier will be discussed again in Chapter 10.

Fig. 7.15 const type qualifier applied to an array parameter.

 1   // Fig. 7.15: fig07_15.cpp
 2   // Demonstrating the const type qualifier.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   void tryToModifyArray( const int [] ); // function prototype
 8
 9   int main()
10   {
11      int a[] = { 102030 };
12
13      tryToModifyArray( a );
14      cout << a[ 0 ] << ' ' << a[ 1 ] << ' ' << a[ 2 ] << ' ';
15
16      return 0// indicates successful termination
17   } // end main
18
19   // In function tryToModifyArray, "b" cannot be used
20   // to modify the original array "a" in main.       
21   void tryToModifyArray( const int b[] )             
22   {                                                  
23      b[ 0 ] /= 2; // compilation error               
24      b[ 1 ] /= 2; // compilation error               
25      b[ 2 ] /= 2; // compilation error               
26   // end function tryToModifyArray                 


Borland C++ command-line compiler error message:

Error E2024 fig07_15.cpp 23: Cannot modify a const object
   in function tryToModifyArray(const int * const)
Error E2024 fig07_15.cpp 24: Cannot modify a const object
   in function tryToModifyArray(const int * const)
Error E2024 fig07_15.cpp 25: Cannot modify a const object
   in function tryToModifyArray(const int * const)


Microsoft Visual C++ compiler error message:

c:cppfp_examplesch07fig07_15fig07_15.cpp(23) : error C3892: 'b' : you
   cannot assign to a variable that is const
c:cppfp_examplesch07fig07_15fig07_15.cpp(24) : error C3892: 'b' : you
   cannot assign to a variable that is const
c:cppfp_examplesch07fig07_15fig07_15.cpp(25) : error C3892: 'b' : you
   cannot assign to a variable that is const


GNU C++ compiler error message:

fig07_15.cpp:23: error: assignment of read-only location
fig07_15.cpp:24: error: assignment of read-only location
fig07_15.cpp:25: error: assignment of read-only location


Common Programming Error 7.11

Common Programming Error 7.11

Forgetting that arrays in the caller are passed by reference, and hence can be modified in called functions, may result in logic errors.

Software Engineering Observation 7.4

Software Engineering Observation 7.4

Applying the const type qualifier to an array parameter in a function definition to prevent the original array from being modified in the function body is another example of the principle of least privilege. Functions should not be given the capability to modify an array unless it is absolutely necessary.

7.6 Case Study: Class GradeBook Using an Array to Store Grades

This section further evolves class GradeBook, introduced in Chapter 3 and expanded in Chapters 46. Recall that this class represents a grade book used by a professor to store and analyze student grades. Previous versions of the class process grades entered by the user, but do not maintain the individual grade values in the class’s data members. Thus, repeat calculations require the user to reenter the grades. One way to solve this problem would be to store each grade entered in an individual data member of the class. For example, we could create data members grade1, grade2, . . ., grade10 in class GradeBook to store 10 student grades. However, the code to total the grades and determine the class average would be cumbersome. In this section, we solve this problem by storing grades in an array.

Storing Student Grades in an Array in Class GradeBook

The version of class GradeBook (Figs. 7.167.17) presented here uses an array of integers to store the grades of several students on a single exam. This eliminates the need to repeatedly input the same set of grades. Array grades is declared as a data member in line 29 of Fig. 7.16—therefore, each GradeBook object maintains its own set of grades.

Fig. 7.16 Definition of class GradeBook using an array to store test grades.

 1   // Fig. 7.16: GradeBook.h
 2   // Definition of class GradeBook that uses an array to store test grades.
 3   // Member functions are defined in GradeBook.cpp
 4
 5   #include <string> // program uses C++ Standard Library string class
 6   using std::string;
 7
 8   // GradeBook class definition
 9   class GradeBook
10   {
11   public:
12      // constant -- number of students who took the test
13      const static int students = 10; // note public data


14
15      // constructor initializes course name and array of grades
16      GradeBook( string, const int [] );
17
18      void setCourseName( string ); // function to set the course name
19      string getCourseName(); // function to retrieve the course name
20      void displayMessage(); // display a welcome message
21      void processGrades(); // perform various operations on the grade data
22      int getMinimum(); // find the minimum grade for the test
23      int getMaximum(); // find the maximum grade for the test
24      double getAverage(); // determine the average grade for the test
25      void outputBarChart(); // output bar chart of grade distribution
26      void outputGrades(); // output the contents of the grades array
27   private:
28      string courseName; // course name for this grade book
29      int grades[ students ]; // array of student grades
30   }; // end class GradeBook


Note that the size of the array in line 29 of Fig. 7.16 is specified by public const static data member students (declared in line 13). This data member is public so that it is accessible to the clients of the class. We’ll soon see an example of a client program using this constant. Declaring students with the const qualifier indicates that this data member is constant—its value cannot be changed after being initialized. Keyword static in this variable declaration indicates that the data member is shared by all objects of the class—all GradeBook objects store grades for the same number of students. Recall from Section 3.6 that when each object of a class maintains its own copy of an attribute, the variable that represents the attribute is known as a data member—each object (instance) of the class has a separate copy of the variable in memory. There are variables for which each object of a class does not have a separate copy. That is the case with static data members, which are also known as class variables. When objects of a class containing static data members are created, all the objects share one copy of the class’s static data members. A static data member can be accessed within the class definition and the member-function definitions like any other data member. As you’ll soon see, a public static data member can also be accessed outside of the class, even when no objects of the class exist, using the class name followed by the binary scope resolution operator (::) and the name of the data member. You’ll learn more about static data members in Chapter 10.

The class’s constructor (declared in line 16 of Fig. 7.16 and defined in lines 17–24 of Fig. 7.17) has two parameters—the course name and an array of grades. When a program creates a GradeBook object (e.g., line 13 of fig07_18.cpp), the program passes an existing int array to the constructor, which copies the array’s values into the data member grades (lines 22–23 of Fig. 7.17). The grade values in the passed array could have been input from a user or read from a file on disk (as we discuss in Chapter 17, File Processing). In our test program, we simply initialize an array with a set of grade values (Fig. 7.18, lines 10–11). Once the grades are stored in data member grades of class GradeBook, all the class’s member functions can access the grades array as needed to perform various calculations.

Fig. 7.17 GradeBook class member functions manipulating an array of grades.

 1   // Fig. 7.17: GradeBook.cpp
 2   // Member-function definitions for class GradeBook that
 3   // uses an array to store test grades.
 4   #include <iostream>
 5   using std::cout;
 6   using std::cin;
 7   using std::endl;
 8   using std::fixed;
 9
10   #include <iomanip>
11   using std::setprecision;
12   using std::setw;
13
14   #include "GradeBook.h" // GradeBook class definition
15
16   // constructor initializes courseName and grades array
17   GradeBook::GradeBook( string name, const int gradesArray[] )
18   {
19      setCourseName( name ); // initialize courseName
20
21      // copy grades from gradesArray to grades data member
22      for ( int grade = 0 ; grade < students; grade++ )    
23         grades[ grade ] = gradesArray[ grade ];           
24   } // end GradeBook constructor
25
26   // function to set the course name
27   void GradeBook::setCourseName( string name )
28   {
29      courseName = name; // store the course name
30   } // end function setCourseName
31


32   // function to retrieve the course name
33   string GradeBook::getCourseName()
34   {
35      return courseName;
36   } // end function getCourseName
37
38   // display a welcome message to the GradeBook user
39   void GradeBook::displayMessage()
40   {
41      // this statement calls getCourseName to get the
42      // name of the course this GradeBook represents
43      cout << "Welcome to the grade book for " << getCourseName() << "!"
44         << endl;
45   } // end function displayMessage
46
47   // perform various operations on the data
48   void GradeBook::processGrades()
49   {
50      // output grades array
51      outputGrades();
52
53      // call function getAverage to calculate the average grade
54      cout << " Class average is " << setprecision( 2  ) << fixed <<
55         getAverage() << endl;
56
57      // call functions getMinimum and getMaximum
58      cout << "Lowest grade is " << getMinimum() << " Highest grade is "
59         << getMaximum() << endl;
60
61      // call function outputBarChart to print grade distribution chart
62      outputBarChart();
63   } // end function processGrades
64
65   // find minimum grade
66   int GradeBook::getMinimum()
67   {
68      int lowGrade = 100 ; // assume lowest grade is 100
69
70      // loop through grades array
71      for ( int grade = 0 ; grade < students; grade++ )
72      {
73         // if current grade lower than lowGrade, assign it to lowGrade
74         if ( grades[ grade ] < lowGrade )                             
75            lowGrade = grades[ grade ]; // new lowest grade            
76      } // end for
77
78      return lowGrade; // return lowest grade
79   } // end function getMinimum
80
81   // find maximum grade
82   int GradeBook::getMaximum()
83   {
84      int highGrade = 0// assume highest grade is 0


85
86      // loop through grades array
87      for ( int grade = 0 ; grade < students; grade++ )
88      {
89         // if current grade higher than highGrade, assign it to highGrade
90         if ( grades[ grade ] > highGrade )                               
91            highGrade = grades[ grade ]; // new highest grade             
92      } // end for
93
94      return highGrade; // return highest grade
95   } // end function getMaximum
96
97   // determine average grade for test
98   double GradeBook::getAverage()
99   {
100     int total = 0// initialize total
101
102     // sum grades in array
103     for ( int grade = 0; grade < students; grade++ )
104        total += grades[ grade ];
105
106     // return average of grades
107     return static_cast< double >( total ) / students;
108  } // end function getAverage
109
110  // output bar chart displaying grade distribution
111  void GradeBook::outputBarChart()
112  {
113     cout << " Grade distribution:" << endl;
114
115     // stores frequency of grades in each range of 10 grades
116     const int frequencySize = 11;
117     int frequency[ frequencySize ] = {}; // initialize elements to 0
118
119     // for each grade, increment the appropriate frequency
120     for ( int grade = 0; grade < students; grade++ )
121        frequency[ grades[ grade ] / 10 ]++;
122
123     // for each grade frequency, print bar in chart
124     for ( int count = 0; count < frequencySize; count++ )
125     {
126        // output bar labels ("0-9:", ..., "90-99:", "100:" )
127        if ( count == 0 )
128           cout << "  0-9: ";
129        else if ( count == 10 )
130           cout << "  100: ";
131        else
132           cout << count * 10 << "-" << ( count * 10 ) + 9 << ": ";
133
134        // print bar of asterisks
135        for ( int stars = 0; stars < frequency[ count ]; stars++ )
136           cout << '*';
137


138        cout << endl; // start a new line of output
139     } // end outer for
140  } // end function outputBarChart
141
142  // output the contents of the grades array
143  void GradeBook::outputGrades()
144  {
145     cout << " The grades are: ";
146
147     // output each student's grade
148     for ( int student = 0; student < students; student++ )
149        cout << "Student " << setw( 2 ) << student + 1 << ": " << setw( 3 )
150           << grades[ student ] << endl;
151  } // end function outputGrades


Member function processGrades (declared in line 21 of Fig. 7.16 and defined in lines 48–63 of Fig. 7.17) contains a series of member function calls that output a report summarizing the grades. Line 51 calls member function outputGrades to print the contents of the array grades. Lines 148–150 in member function outputGrades use a for statement to output each student’s grade. Although array indices start at 0, a professor would typically number students starting at 1. Thus, lines 149–150 output student + 1 as the student number to produce grade labels "Student 1: ", "Student 2: ", and so on.

Member function processGrades next calls member function getAverage (lines 54–55) to obtain the average of the grades in the array. Member function getAverage (declared in line 24 of Fig. 7.16 and defined in lines 98–108 of Fig. 7.17) uses a for statement to total the values in array grades before calculating the average. Note that the averaging calculation in line 107 uses const static data member students to determine the number of grades being averaged.

Lines 58–59 in member function processGrades call member functions getMinimum and getMaximum to determine the lowest and highest grades of any student on the exam, respectively. Let’s examine how member function getMinimum finds the lowest grade. Because the highest grade allowed is 100, we begin by assuming that 100 is the lowest grade (line 68). Then, we compare each of the elements in the array to the lowest grade, looking for smaller values. Lines 71–76 in member function getMinimum loop through the array, and lines 74–75 compare each grade to lowGrade. If a grade is less than lowGrade, lowGrade is set to that grade. When line 78 executes, lowGrade contains the lowest grade in the array. Member function getMaximum (lines 82–95) works similarly to member function getMinimum.

Finally, line 62 in member function processGrades calls member function outputBarChart to print a distribution chart of the grade data using a technique similar to that in Fig. 7.9. In that example, we manually calculated the number of grades in each category (i.e., 0–9, 10–19, . . ., 90–99 and 100) by simply looking at a set of grades. In this example, lines 120–121 use a technique similar to that in Fig. 7.10 and Fig. 7.11 to calculate the frequency of grades in each category. Line 117 declares and creates array frequency of 11 ints to store the frequency of grades in each grade category. For each grade in array grades, lines 120–121 increment the appropriate element of the frequency array. To determine which element to increment, line 121 divides the current grade by 10 using integer division. For example, if grade is 85, line 121 increments frequency[ 8 ] to update the count of grades in the range 80–89. Lines 124–139 next print the bar chart (see Fig. 7.18) based on the values in array frequency. Like lines 29–30 of Fig. 7.9, lines 135–136 of Fig. 7.17 use a value in array frequency to determine the number of asterisks to display in each bar.

Fig. 7.18 Creates a GradeBook object using an array of grades, then invokes member function processGrades to analyze them.

 1   // Fig. 7.18: fig07_18.cpp
 2   // Creates GradeBook object using an array of grades.
 3
 4   #include "GradeBook.h" // GradeBook class definition
 5


 6   // function main begins program execution
 7   int main()
 8   {
 9      // array of student grades
10      int gradesArray[ GradeBook::students ] =
11         { 876894100837885917687 };
12
13      GradeBook myGradeBook(
14         "CS101 Introduction to C++ Programming", gradesArray );
15      myGradeBook.displayMessage();
16      myGradeBook.processGrades();
17      return 0;
18   } // end main


Welcome to the grade book for
CS101 Introduction to C++ Programming!

The grades are:

Student  1:  87
Student  2:  68
Student  3:  94
Student  4: 100
Student  5:  83
Student  6:  78
Student  7:  85
Student  8:  91
Student  9:  76
Student 10:  87

Class average is 84.90
Lowest grade is 68
Highest grade is 100

Grade distribution:
  0-9:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
  100: *


Testing Class GradeBook

The program of Fig. 7.18 creates an object of class GradeBook (Figs. 7.167.17) using the int array gradesArray (declared and initialized in lines 10–11). Note that we use the binary scope resolution operator (::) in the expression “GradeBook::students” (line 10) to access class GradeBook’s static constant students. We use this constant here to create an array that is the same size as array grades stored as a data member in class GradeBook. Lines 13–14 pass a course name and gradesArray to the GradeBook constructor. Line 15 displays a welcome message, and line 16 invokes the GradeBook object’s processGrades member function. The output reveals the summary of the 10 grades in myGradeBook.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset