In this chapter you’ll learn:
To use the array data structure to represent a set of related data items.
To use arrays to store, sort and search lists and tables of values.
To declare arrays, initialize arrays and refer to the individual elements of arrays.
To pass arrays to functions.
Basic searching and sorting techniques.
To declare and manipulate multidimensional arrays.
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
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 3–6. 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.
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.
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;
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 |
||||
|
|
|
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 |
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 arrayName[ arraySize ];
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
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.
This section presents many examples that demonstrate how to declare arrays, how to initialize arrays and how to perform common array manipulations.
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.
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).
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[] = { 1, 2, 3, 4, 5 };
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 ] = { 32, 27, 64, 18, 95, 14 };
causes a compilation error, because there are six initializers and only five array elements.
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
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()
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'
Not assigning a value to a constant variable when it is declared is a compilation error.
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.
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.
Defining the size of each array as a constant variable instead of a literal constant can make programs more scalable.
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.
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.16–7.17 and Figs. 7.23–7.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 ] = { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
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 ]
.
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.16–7.17 and Figs. 7.23–7.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 ] = { 0, 0, 0, 0, 0, 0, 1, 2, 4, 2, 1 };
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.
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).
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, 2, 6, 4, 8, 5, 9, 7, 8,
18 10, 1, 6, 3, 8, 6, 10, 3, 8, 2, 7, 6, 5, 7, 6, 8, 6, 7,
19 5, 6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10 };
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 ]
.
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.
Referring to an element outside the array bounds is an execution-time logic error. It is not a syntax error.
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.
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 vector
s 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.
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
).
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.
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.
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 ] = { 1, 2, 3 }; // 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
.
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 ];
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.
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.
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 ] = { 0, 1, 2, 3, 4 }; // 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[] = { 10, 20, 30 };
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)
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
Forgetting that arrays in the caller are passed by reference, and hence can be modified in called functions, may result in logic errors.
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.
This section further evolves class GradeBook
, introduced in Chapter 3 and expanded in Chapters 4–6. 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.
The version of class GradeBook
(Figs. 7.16–7.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 int
s 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 { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
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: *
The program of Fig. 7.18 creates an object of class GradeBook
(Figs. 7.16–7.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
.