Chapter 8. Arrays

 

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

 
 --Lewis Carroll
 

Now go, write it before them in a table, and note it in a book.

 
 --Isaiah 30:8
 

To go beyond is as wrong as to fall short.

 
 --Confucius
<feature> <supertitle>Objectives</supertitle>

In this chapter you’ll learn:

<objective>

To use arrays to store data in and retrieve data from lists and tables of values.

</objective>
<objective>

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

</objective>
<objective>

To use foreach to iterate through arrays.

</objective>
<objective>

To use implicitly typed local variables.

</objective>
<objective>

To pass arrays to methods.

</objective>
<objective>

To declare and manipulate multidimensional arrays.

</objective>
<objective>

To write methods that use variable-length argument lists.

</objective>
<objective>

To read command-line arguments into an application.

</objective>
</feature>
<feature> <supertitle>Outline</supertitle> </feature>

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. Arrays are fixed-length entities—they remain the same length once they’re created, although an array variable may be reassigned such that it refers to a new array of a different length.

After discussing how arrays are declared, created and initialized, we present examples that demonstrate several common array manipulations. We use arrays to simulate shuffling and dealing playing cards. The chapter demonstrates C#’s last structured control statement—the foreach repetition statement—which provides a concise notation for accessing data in arrays (and other data structures, as you’ll see in Chapter 9 and later in the book). We enhance the GradeBook case study using arrays to enable the class to store a set of grades and analyze student grades from multiple exams.

Arrays

An array is a group of variables (called elements) containing values that all have the same type. Recall that types are divided into two categories—value types and reference types. Arrays are reference types. As you’ll see, what we typically think of as an array is actually a reference to an array object. The elements of an array can be either value types or reference types, including other arrays. To refer to a particular element in an array, we specify the name of the reference to the array and the position number of the element in the array, which is known as the element’s index.

Figure 8.1 shows a logical representation of an integer array called c. This array contains 12 elements. An application refers to any one of these elements with an array-access expression that includes the name of the array, followed by the index of the particular element in square brackets ([]). The first element in every array has index zero and is sometimes called the zeroth element. Thus, the elements of array c are c[0], c[1], c[2] and so on. The highest index in array c is 11, which is one less than the number of elements in the array, because indices begin at 0. Array names follow the same conventions as other variable names.

A 12-element array.

Figure 8.1. A 12-element array.

An index must be a nonnegative integer and can be an expression. For example, if we assume that variable a is 5 and variable b is 6, then the statement

c[ a + b ] += 2;

adds 2 to array element c[ 11 ]. An indexed array name is an array-access expression. Such expressions can be used on the left side of an assignment (i.e., an lvalue) to place a new value into an array element. The array index must be a value of type int, uint, long or ulong, or a value of a type that can be implicitly promoted to one of these types.

Let’s examine array c in Fig. 8.1 more closely. The name of the variable that references the array is c. Every array instance knows its own length and provides access to this information with the Length property. For example, the expression c.Length uses array c’s Length property to determine the length of the array (that is, 12). The Length property of an array cannot be changed, because it does not provide a set accessor. The array’s 12 elements are referred to as c[0], c[1], c[2], ..., c[11]. Referring to elements outside of this range, such as c[-1] or c[12], is a runtime error (as we’ll demonstrate in Fig. 8.8). 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 calculate the sum of the values contained in the first three elements of array c and store the result in variable sum, we would write

sum = c[ 0 ] + c[ 1 ] + c[ 2 ];

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

x = c[ 6 ] / 2;

Declaring and Creating Arrays

Arrays occupy space in memory. Since they’re objects, they’re typically created with keyword new. To create an array object, you specify the type and the number of array elements as part of an array-creation expression that uses keyword new. Such an expression returns a reference that can be stored in an array variable. The following declaration and array-creation expression create an array object containing 12 int elements and store the array’s reference in variable c:

int[] c = new int[ 12 ];

This expression can be used to create the array shown in Fig. 8.1 (but not the initial values in the array—we’ll show how to initialize the elements of an array momentarily). This task also can be performed as follows:

int[] c; // declare the array variable
c = new int[ 12 ]; // create the array; assign to array variable

In the declaration, the square brackets following the type int indicate that c is a variable that will refer to an array of ints (i.e., c will store a reference to an array object). In the assignment statement, the array variable c receives the reference to a new array object of 12 int elements. The number of elements can also be specified as an expression that’s calculated at execution time. When an array is created, each element of the array receives a default value—0 for the numeric simple-type elements, false for bool elements and null for references. As we’ll soon see, we can provide specific, nondefault initial element values when we create an array.

Common Programming Error 8.1

Common Programming Error 8.1

In the declaration of a variable that will refer to an array, specifying the number of elements in the square brackets (e.g., int[ 12 ] c;) is a syntax error.

An application can create several arrays in a single declaration. The following statement reserves 100 elements for string array b and 27 elements for string array x:

string[] b = new string[ 100 ], x = new string[ 27 ];

In this statement, string[] applies to each variable. For readability and ease of commenting, we prefer to split the preceding statement into two statements, as in:

string[] b = new string[ 100 ]; // create string array b
string[] x = new string[ 27 ]; // create string array x

An application can declare variables that will refer to arrays of value-type elements or reference-type elements. For example, every element of an int array is an int value, and every element of a string array is a reference to a string object.

Resizing an Array

Though arrays are fixed-length entities, you can use the static Array method Resize, which takes two arguments—the array to be resized and the new length—to create a new array with the specified length. This method copies the contents of the old array into the new array and sets the variable it receives as its first argument to reference the new array. For example, consider the following statements:

int[] newArray = new int[ 5 ];
Array.Resize( ref newArray, 10 );

The variable newArray initially refers to a five-element array. The resize method sets newArray to refer to a new 10-element array. If the new array is smaller than the old array, any content that cannot fit into the new array is truncated without warning.

Examples Using Arrays

This section presents several examples that demonstrate declaring arrays, creating arrays, initializing arrays and manipulating array elements.

Creating and Initializing an Array

The application of Fig. 8.2 uses keyword new to create an array of five int elements that are initially 0 (the default for int variables).

Example 8.2. Creating an array.

 1   // Fig. 8.2: InitArray.cs
 2   // Creating an array.
 3   using System;
 4
 5   public class InitArray
 6   {
 7      public static void Main( string[] args )
 8      {
 9         int[] array; // declare array named array
10
11         // create the space for array and initialize to default zeros
12         array = new int[ 5 ]; // 5 int elements
13
14         Console.WriteLine( "{0}{1,8}", "Index", "Value" ); // headings
15
16         // output each array element's value                            
17         for ( int counter = 0; counter < array.Length; counter++ )      
18            Console.WriteLine( "{0,5}{1,8}", counter, array[ counter ] );
19      } // end Main
20   } // end class InitArray
Index   Value
    0       0
    1       0
    2       0
    3       0
    4       0

Line 9 declares array—a variable capable of referring to an array of int elements. Line 12 creates the five-element array object and assigns its reference to variable array. Line 14 outputs the column headings. The first column contains the index (04) of each array element, and the second column contains the default value (0) of each array element right justified in a field width of 8.

The for statement in lines 17–18 outputs the index number (represented by counter) and the value (represented by array[counter]) of each array element. The loop-control variable counter is initially 0—index values start at 0, so using zero-based counting allows the loop to access every element of the array. The for statement’s loop-continuation condition uses the property array.Length (line 17) to obtain the length of the array. In this example, the length of the array is 5, so the loop continues executing as long as the value of control variable counter is less than 5. The highest index value of a five-element array is 4, so using the less-than operator in the loop-continuation condition guarantees that the loop does not attempt to access an element beyond the end of the array (i.e., during the final iteration of the loop, counter is 4). We’ll soon see what happens when an out-of-range index is encountered at execution time.

Using an Array Initializer

An application can create an array and initialize its elements with an array initializer, which is a comma-separated list of expressions (called an initializer list) enclosed in braces. In this case, the array length is determined by the number of elements in the initializer list. For example, the declaration

int[] n = { 10, 20, 30, 40, 50 };

creates a five-element array with index values 0, 1, 2, 3 and 4. Element n[0] is initialized to 10, n[1] is initialized to 20 and so on. This statement does not require new to create the array object. When the compiler encounters an array initializer list, it counts the number of initializers in the list to determine the array’s size, then sets up the appropriate new operation “behind the scenes.” The application in Fig. 8.3 initializes an integer array with 10 values (line 10) and displays the array in tabular format. The code for displaying the array elements (lines 15–16) is identical to that in Fig. 8.2 (lines 17–18).

Example 8.3. Initializing the elements of an array with an array initializer.

 1   // Fig. 8.3: InitArray.cs
 2   // Initializing the elements of an array with an array initializer.
 3   using System;
 4
 5   public class InitArray
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // initializer list specifies the value for each element 
10         int[] array = { 32, 27, 64, 18, 95, 14, 90, 70, 60, 37 };
11
12         Console.WriteLine( "{0}{1,8}", "Index", "Value" ); // headings
13
14         // output each array element's value
15         for ( int counter = 0; counter < array.Length; counter++ )
16            Console.WriteLine( "{0,5}{1,8}", counter, array[ counter ] );
17      } // end Main
18   } // end class InitArray
Index   Value
    0      32
    1      27
    2      64
    3      18
    4      95
    5      14
    6      90
    7      70
    8      60
    9      37

Calculating a Value to Store in Each Array Element

Some applications calculate the value to be stored in each array element. The application in Fig. 8.4 creates a 10-element array and assigns to each element one of the even integers from 2 to 20 (2, 4, 6, ..., 20). Then the application displays the array in tabular format. The for statement at lines 13–14 calculates an array element’s value by multiplying the current value of the for loop’s control variable counter by 2, then adding 2.

Example 8.4. Calculating values to be placed into the elements of an array.

 1   // Fig. 8.4: InitArray.cs
 2   // Calculating values to be placed into the elements of an array.
 3   using System;
 4
 5   public class InitArray
 6   {
 7      public static void Main( string[] args )
 8      {
 9         const int ARRAY_LENGTH = 10; // create a named constant
10         int[] array = new int[ ARRAY_LENGTH ]; // create array 
11
12         // calculate value for each array element
13         for ( int counter = 0; counter < array.Length; counter++ )
14            array[ counter ] = 2 + 2 * counter;
15
16         Console.WriteLine( "{0}{1,8}", "Index", "Value" ); // headings
17
18         // output each array element's value
19         for ( int counter = 0; counter < array.Length; counter++ )
20            Console.WriteLine( "{0,5}{1,8}", counter, array[ counter ] );
21      } // end Main
22   } // end class InitArray
Index   Value
    0       2
    1       4
    2       6
    3       8
    4      10
    5      12
    6      14
    7      16
    8      18
    9      20

Line 9 uses the modifier const to declare the constant ARRAY_LENGTH, whose value is 10. Constants must be initialized when they’re declared and cannot be modified thereafter. We declare constants with all capital letters by convention to make them stand out in the code.

Common Programming Error 8.2

Common Programming Error 8.2

Assigning a value to a named constant after it’s been initialized is a compilation error.

Common Programming Error 8.3

Common Programming Error 8.3

Attempting to declare a named constant without initializing it is a compilation error.

Good Programming Practice 8.1

Good Programming Practice 8.1

Constants also are called named constants. Applications using constants often are more readable than those that use literal values (e.g., 10)—a named constant such as ARRAY_LENGTH clearly indicates its purpose, whereas a literal value could have different meanings based on the context in which it’s used. Another advantage to using named constants is that if the value of the constant must be changed, the change is necessary only in the declaration, thus reducing the cost of maintaining the code.

Good Programming Practice 8.2

Good Programming Practice 8.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.

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, an instructor may wish to total the elements and use that total to calculate the class average for the exam. The GradeBook examples later in the chapter (Fig. 8.15 and Fig. 8.20) use this technique.

The application in Fig. 8.5 sums the values contained in a 10-element integer array. The application creates and initializes the array at line 9. The for statement performs the calculations. [Note: The values supplied as array initializers are often read into an application, rather than specified in an initializer list. For example, an application could input the values from a user or from a file on disk (as discussed in Chapter 17, Files and Streams). Reading the data into an application makes the application more reusable, because it can be used with different sets of data.]

Example 8.5. Computing the sum of the elements of an array.

 1   // Fig. 8.5: SumArray.cs
 2   // Computing the sum of the elements of an array.
 3   using System;
 4
 5   public class SumArray
 6   {
 7      public static void Main( string[] args )
 8      {
 9         int[] array = { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
10         int total = 0;
11
12         // add each element's value to total                      
13         for ( int counter = 0; counter < array.Length; counter++ )
14            total += array[ counter ];                             
15
16         Console.WriteLine( "Total of array elements: {0}", total );
17      } // end Main
18   } // end class SumArray
Total of array elements: 849

Using Bar Charts to Display Array Data Graphically

Many applications 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 (*).

An instructor might graph the number of grades in each of several categories to visualize the grade distribution for an exam. Suppose the grades on an exam were 87, 68, 94, 100, 83, 78, 85, 91, 76 and 87. 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 application (Fig. 8.6) stores this grade distribution data in an array of 11 elements, each corresponding to a category of grades. For example, array[0] indicates the number of grades in the range 0–9, array[7] the number of grades in the range 70–79 and array[10] the number of 100 grades. The two versions of class GradeBook later in the chapter (Figs. 8.15 and 8.20) contain code that calculates these grade frequencies based on a set of grades. For now, we manually create array by examining the set of grades and initializing the elements of array to the number of values in each range (line 9).

Example 8.6. Bar chart displaying application.

 1   // Fig. 8.6: BarChart.cs
 2   // Bar chart displaying application.
 3   using System;
 4
 5   public class BarChart
 6   {
 7      public static void Main( string[] args )
 8      {
 9         int[] array = { 0, 0, 0, 0, 0, 0, 1, 2, 4, 2, 1 }; // distribution
10
11         Console.WriteLine( "Grade distribution:" );
12
13         // for each array element, output a bar of the chart
14         for ( int counter = 0; counter < array.Length; counter++ )
15         {
16            // output bar labels ( "00-09: ", ..., "90-99: ", "100: " )
17            if ( counter == 10 )
18               Console.Write( " 100: " );
19            else
20               Console.Write( "{0:D2}-{1:D2}: ",
21                  counter * 10, counter * 10 + 9 );
22
23            // display bar of asterisks                             
24            for ( int stars = 0; stars < array[ counter ]; stars++ )
25               Console.Write( "*" );                                
26
27            Console.WriteLine(); // start a new line of output
28         } // end outer for
29      } // end Main
30   } // end class BarChart

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

The application reads the numbers from the array and graphs the information as a bar chart. Each grade range is followed by a bar of asterisks indicating the number of grades in that range. To label each bar, lines 17–21 output a grade range (e.g., "70-79: ") based on the current value of counter. When counter is 10, line 18 outputs " 100: " to align the colon with the other bar labels. When counter is not 10, line 20 uses the format items {0:D2} and {1:D2} to output the label of the grade range. The format specifier D indicates that the value should be formatted as an integer, and the number after the D indicates how many digits this formatted integer must contain. The 2 indicates that values with fewer than two digits should begin with a leading 0.

The nested for statement (lines 24–25) outputs the bars. Note the loop-continuation condition at line 24 (stars < array[ counter ]). Each time the application reaches the inner for, the loop counts from 0 up to one less than array[ counter ], thus using a value in array to determine the number of asterisks to display. In this example, array[0]array[5] contain 0s because no students received a grade below 60. Thus, the application displays no asterisks next to the first six grade ranges.

Using the Elements of an Array as Counters

Sometimes, applications use counter variables to summarize data, such as the results of a survey. In Fig. 7.7, we used separate counters in our die-rolling application to track the number of times each face of a six-sided die appeared as the application rolled the die 6000 times. An array version of the application in Fig. 7.7 is shown in Fig. 8.7.

Example 8.7. Roll a six-sided die 6000 times.

 1   // Fig. 8.7: RollDie.cs
 2   // Roll a six-sided die 6000 times.
 3   using System;
 4
 5   public class RollDie
 6   {
 7      public static void Main( string[] args )
 8      {
 9         Random randomNumbers = new Random(); // random-number generator
10         int[] frequency = new int[ 7 ]; // array of frequency counters
11
12         // roll die 6000 times; use die value as frequency index
13         for ( int roll = 1; roll <= 6000; roll++ )
14            ++frequency[ randomNumbers.Next( 1, 7 ) ];
15
16         Console.WriteLine( "{0}{1,10}", "Face", "Frequency" );
17
18         // output each array element's value
19         for ( int face = 1; face < frequency.Length; face++ )
20            Console.WriteLine( "{0,4}{1,10}", face, frequency[ face ] );
21      } // end Main
22   } // end class RollDie

Face Frequency
   1       956
   2       981
   3      1001
   4      1030
   5      1035
   6       997

Figure 8.7 uses array frequency (line 10) to count the occurrences of each side of the die. The single statement in line 14 of this application replaces lines 26–46 of Fig. 7.7. Line 14 uses the random value to determine which frequency element to increment during each iteration of the loop. The calculation in line 14 produces random numbers from 1 to 6, so array frequency must be large enough to store six counters. We use a seven-element array in which we ignore frequency[0]—it’s more logical to have the face value 1 increment frequency[1] than frequency[0]. Thus, each face value is used as an index for array frequency. We also replaced lines 50–52 of Fig. 7.7 by looping through array frequency to output the results (Fig. 8.7, lines 19–20).

Using Arrays to Analyze Survey Results

Our next example uses arrays to summarize data collected in a survey. Consider the following problem statement:

Twenty students were asked to rate on a scale of 1 to 5 the quality of the food in the student cafeteria, with 1 being “awful” and 5 being “excellent.” Place the 20 responses in an integer array and determine the frequency of each rating.

This is a typical array-processing application (Fig. 8.8). We wish to summarize the number of responses of each type (that is, 1–5). Array responses (lines 10–11) is a 20-element integer array containing the students’ survey responses. The last value in the array is intentionally an incorrect response (14). When a C# program executes, array element indices are checked for validity—all indices must be greater than or equal to 0 and less than the length of the array. Any attempt to access an element outside that range of indices results in a runtime error that is known as an IndexOutOfRangeException. At the end of this section, we’ll discuss the invalid response value, demonstrate array bounds checking and introduce C#’s exception-handling mechanism, which can be used to detect and handle an IndexOutOfRangeException.

Example 8.8. Poll analysis application.

 1   // Fig. 8.8: StudentPoll.cs
 2   // Poll analysis application.
 3   using System;
 4
 5   public class StudentPoll
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // student response array (more typically, input at run time)
10         int[] responses = { 1, 2, 5, 4, 3, 5, 2, 1, 3, 3, 1, 4, 3, 3, 3,
11            2, 3, 3, 2, 14 };
12         int[] frequency = new int[ 6 ]; // array of frequency counters
13
14         // for each answer, select responses element and use that value
15         // as frequency index to determine element to increment        
16         for ( int answer = 0; answer < responses.Length; answer++ )    
17         {                                                              
18            try                                                         
19            {                                                           
20               ++frequency[ responses[ answer ] ];                      
21            } // end try                                                
22            catch ( IndexOutOfRangeException ex )                       
23            {                                                           
24               Console.WriteLine( ex.Message );                         
25               Console.WriteLine( "   responses({0}) = {1}
",          
26                  answer, responses[ answer ] );                        
27            } // end catch                                              
28         } // end for                                                   
29
30         Console.WriteLine( "{0}{1,10}", "Rating", "Frequency" );
31
32         // output each array element's value
33         for ( int rating = 1; rating < frequency.Length; rating++ )
34            Console.WriteLine( "{0,6}{1,10}", rating, frequency[ rating ] );
35      } // end Main
36   } // end class StudentPoll
Index was outside the bounds of the array.
   responses(19) = 14

Rating Frequency
     1         3
     2         4
     3         8
     4         2
     5         2

The frequency Array

We use the six-element array frequency (line 12) to count the number of occurrences of each response. Each element is used as a counter for one of the possible types of survey responses—frequency[1] counts the number of students who rated the food as 1, frequency[2] counts the number of students who rated the food as 2, and so on.

Summarizing the Results

The for statement (lines 16–28) reads the responses from the array responses one at a time and increments one of the counters frequency[1] to frequency[5]; we ignore frequency[0] because the survey responses are limited to the range 1–5. The key statement in the loop appears in line 20. This statement increments the appropriate frequency counter as determined by the value of responses[answer].

Let’s step through the first few iterations of the for statement:

  1. When the counter answer is 0, responses[answer] is the value of responses[0] (that is, 1—see line 10). In this case, frequency[responses[answer]] is interpreted as frequency[1], and the counter frequency[1] is incremented by one. To evaluate the expression, we begin with the value in the innermost set of brackets (answer, currently 0). The value of answer is plugged into the expression, and the next set of brackets (responses[answer]) is evaluated. That value is used as the index for the frequency array to determine which counter to increment (in this case, frequency[1]).

  2. The next time through the loop answer is 1, responses[answer] is the value of responses[1] (that is, 2—see line 10), so frequency[responses[answer]] is interpreted as frequency[2], causing frequency[2] to be incremented.

  3. When answer is 2, responses[answer] is the value of responses[2] (that is, 5—see line 10), so frequency[responses[answer]] is interpreted as frequency[5], causing frequency[5] to be incremented, and so on.

Regardless of the number of responses processed in the survey, only a six-element array (in which we ignore element zero) is required to summarize the results, because all the correct response values are between 1 and 5, and the index values for a six-element array are 0–5. In the output in Fig. 8.8, the frequency column summarizes only 19 of the 20 values in the responses array—the last element of the array responses contains an incorrect response that was not counted.

Exception Handling: Processing the Incorrect Response

An exception indicates a problem that occurs while a program executes. The name “exception” suggests that the problem occurs infrequently—if the “rule” is that a statement normally executes correctly, then the problem represents the “exception to the rule.” Exception handling enables you to create fault-tolerant programs that can resolve (or handle) exceptions. In many cases, this allows a program to continue executing as if no problems were encountered. For example, the Student Poll application still displays results (Fig. 8.8), even though one of the responses was out of range. More severe problems might prevent a program from continuing normal execution, instead requiring the program to notify the user of the problem, then terminate. When the runtime or a method detects a problem, such as an invalid array index or an invalid method argument, it throws an exception—that is, an exception occurs.

The try Statement

To handle an exception, place any code that might throw an exception in a try statement (lines 18–27). The try block (lines 18–21) contains the code that might throw an exception, and the catch block (lines 22–27) contains the code that handles the exception if one occurs. You can have many catch blocks to handle different types of exceptions that might be thrown in the corresponding try block. When line 20 correctly increments an element of the frequency array, lines 22–27 are ignored. The braces that delimit the bodies of the try and catch blocks are required.

Executing the catch Block

When the program encounters the value 14 in the responses array, it attempts to add 1 to frequency[14], which does not exist—the frequency array has only six elements. Because array bounds checking is performed at execution time, the Common Language Runtime generates an exception—specifically line 20 throws an IndexOutOfRangeException to notify the program of this problem. At this point the try block terminates and the catch block begins executing—if you declared any variables in the try block, they’re now out of scope and are not accessible in the catch block.

The catch block declares a type (IndexOutOfRangeException) and an exception parameter (ex). The catch block can handle exceptions of the specified type. Inside the catch block, you can use the parameter’s identifier to interact with a caught exception object.

Error-Prevention Tip 8.1

Error-Prevention Tip 8.1

When writing code to access an array element, ensure that the array index remains greater than or equal to 0 and less than the length of the array. This helps prevent IndexOutOfRangeExceptions in your program.

Message Property of the Exception Parameter

When lines 22–27 catch the exception, the program displays a message indicating the problem that occurred. Line 24 uses the exception object’s Message property to get the error message that is stored in the exception object and display it. Once the message is displayed in this example, the exception is considered handled and the program continues with the next statement after the catch block’s closing brace. In this example, the end of the for statement is reached (line 28), so the program continues with the increment of the control variable in line 16. We use exception handling again in Chapter 10 and Chapter 13 presents a deeper look at exception handling.

Case Study: Card Shuffling and Dealing Simulation

So far, this chapter’s examples have used arrays of value-type elements. This section uses random-number generation and an array of reference-type elements—namely, objects representing playing cards—to develop a class that simulates card shuffling and dealing. This class can then be used to implement applications that play card games. The exercises at the end of the chapter use the techniques developed here to build a poker application.

We first develop class Card (Fig. 8.9), which represents a playing card that has a face (e.g., "Ace", "Deuce", "Three", ..., "Jack", "Queen", "King") and a suit (e.g., "Hearts", "Diamonds", "Clubs", "Spades"). Next, we develop class DeckOfCards (Fig. 8.10), which creates a deck of 52 playing cards in which each element is a Card object. Then we build an application (Fig. 8.11) that uses class DeckOfCards’s card shuffling and dealing capabilities.

Example 8.9. Card class represents a playing card.

 1   // Fig. 8.9: Card.cs
 2   // Card class represents a playing card.
 3   public class Card
 4   {
 5      private string face; // face of card ("Ace", "Deuce", ...)
 6      private string suit; // suit of card ("Hearts", "Diamonds", ...)
 7
 8      // two-parameter constructor initializes card's face and suit
 9      public Card( string cardFace, string cardSuit )
10      {
11         face = cardFace; // initialize face of card
12         suit = cardSuit; // initialize suit of card
13      } // end two-parameter Card constructor
14
15      // return string representation of Card
16      public override string ToString()
17      {                                
18         return face + " of " + suit;  
19      } // end method ToString         
20   } // end class Card

Class Card

Class Card (Fig. 8.9) contains two string instance variables—face and suit—that are used to store references to the face value and suit name for a specific Card. The constructor for the class (lines 9–13) receives two strings that it uses to initialize face and suit. Method ToString (lines 16–19) creates a string consisting of the face of the card, the string " of " and the suit of the card. Recall from Chapter 7 that the + operator can be used to concatenate (i.e., combine) several strings to form one larger string. Card’s ToString method can be invoked explicitly to obtain a string representation of a Card object (e.g., "Ace of Spades"). The ToString method of an object is called implicitly in many cases when the object is used where a string is expected (e.g., when WriteLine outputs the object or when the object is concatenated to a string using the + operator). For this behavior to occur, ToString must be declared with the header exactly as shown in line 16 of Fig. 8.9. We’ll explain the purpose of the override keyword in more detail when we discuss inheritance in Chapter 11.

Class DeckOfCards

Class DeckOfCards (Fig. 8.10) declares an instance-variable named deck that will refer to an array of Card objects (line 7). Like simple-type array variable declarations, the declaration of a variable for an array of objects (e.g., Card[] deck) includes the type of the elements in the array, followed by square brackets and the name of the array variable. Class DeckOfCards also declares int instance variable currentCard (line 8), representing the next Card to be dealt from the deck array, and named constant NUMBER_OF_CARDS (line 9), indicating the number of Cards in the deck (52).

Class DeckOfCards: Constructor

The class’s constructor instantiates the deck array (line 19) to be of size NUMBER_OF_CARDS. When first created, the elements of the deck array are null by default, so the constructor uses a for statement (lines 24–26) to fill the deck array with Cards. The for statement initializes control variable count to 0 and loops while count is less than deck.Length, causing count to take on each integer value from 0 to 51 (the indices of the deck array). Each Card is instantiated and initialized with two strings—one from the faces array (which contains the strings "Ace" through "King") and one from the suits array (which contains the strings "Hearts", "Diamonds", "Clubs" and "Spades"). The calculation count % 13 always results in a value from 0 to 12 (the 13 indices of the faces array in lines 15–16), and the calculation count / 13 always results in a value from 0 to 3 (the four indices of the suits array in line 17). When the deck array is initialized, it contains the Cards with faces "Ace" through "King" in order for each suit.

Example 8.10. DeckOfCards class represents a deck of playing cards.

 1   // Fig. 8.10: DeckOfCards.cs
 2   // DeckOfCards class represents a deck of playing cards.
 3   using System;
 4
 5   public class DeckOfCards
 6   {
 7      private Card[] deck; // array of Card objects
 8      private int currentCard; // index of next Card to be dealt (0-51)
 9      private const int NUMBER_OF_CARDS = 52; // constant number of Cards
10      private Random randomNumbers; // random-number generator
11
12      // constructor fills deck of Cards
13      public DeckOfCards()
14      {
15         string[] faces = { "Ace", "Deuce", "Three", "Four", "Five", "Six",
16            "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King" };    
17         string[] suits = { "Hearts", "Diamonds", "Clubs", "Spades" };     
18
19         deck = new Card[ NUMBER_OF_CARDS ]; // create array of Card objects
20         currentCard = 0; // set currentCard so deck[ 0 ] is dealt first
21         randomNumbers = new Random(); // create random-number generator
22
23         // populate deck with Card objects                         
24         for ( int count = 0; count < deck.Length; count++ )        
25            deck[ count ] =                                         
26               new Card( faces[ count % 13 ], suits[ count / 13 ] );
27      } // end DeckOfCards constructor
28
29      // shuffle deck of Cards with one-pass algorithm
30      public void Shuffle()
31      {
32         // after shuffling, dealing should start at deck[ 0 ] again
33         currentCard = 0; // reinitialize currentCard
34
35         // for each Card, pick another random Card and swap them
36         for ( int first = 0; first < deck.Length; first++ )
37         {
38            // select a random number between 0 and 51
39            int second = randomNumbers.Next( NUMBER_OF_CARDS );
40
41            // swap current Card with randomly selected Card
42            Card temp = deck[ first ];     
43            deck[ first ] = deck[ second ];
44            deck[ second ] = temp;         
45         } // end for
46      } // end method Shuffle
47
48      // deal one Card
49      public Card DealCard()
50      {
51         // determine whether Cards remain to be dealt
52         if ( currentCard < deck.Length )
53            return deck[ currentCard++ ]; // return current Card in array
54         else
55            return null; // indicate that all Cards were dealt
56      } // end method DealCard
57   } // end class DeckOfCards

Class DeckOfCards: Shuffle Method

Method Shuffle (lines 30–46) shuffles the Cards in the deck. The method loops through all 52 Cards (array indices 0 to 51). For each Card, a number between 0 and 51 is picked randomly to select another Card. Next, the current Card object and the randomly selected Card object are swapped in the array. This exchange is performed by the three assignments in lines 42–44. The extra variable temp temporarily stores one of the two Card objects being swapped. The swap cannot be performed with only the two statements

deck[ first ] = deck[ second ];
deck[ second ] = deck[ first ];

If deck[ first ] is the "Ace" of "Spades" and deck[ second ] is the "Queen" of "Hearts", then after the first assignment, both array elements contain the "Queen" of "Hearts", and the "Ace" of "Spades" is lost—hence, the extra variable temp is needed. After the for loop terminates, the Card objects are randomly ordered. Only 52 swaps are made in a single pass of the entire array, and the array of Card objects is shuffled. [Note: It’s recommended that you use a so-called unbiased shuffling algorithm for real card games. Such an algorithm ensures that all possible shuffled card sequences are equally likely to occur. A popular unbiased shuffling algorithm is the Fisher-Yates algorithm—en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle. This page also shows how to implement the algorithm in several programming languages.]

Class DeckOfCards: DealCard Method

Method DealCard (lines 49–56) deals one Card in the array. Recall that currentCard indicates the index of the next Card to be dealt (i.e., the Card at the top of the deck). Thus, line 52 compares currentCard to the length of the deck array. If the deck is not empty (i.e., currentCard is less than 52), line 53 returns the top Card and increments currentCard to prepare for the next call to DealCard—otherwise, null is returned.

Shuffling and Dealing Cards

The application of Fig. 8.11 demonstrates the card shuffling and dealing capabilities of class DeckOfCards (Fig. 8.10). Line 10 creates a DeckOfCards object named myDeckOfCards. Recall that the DeckOfCards constructor creates the deck with the 52 Card objects in order by suit and face. Line 11 invokes myDeckOfCards’s Shuffle method to rearrange the Card objects. The for statement in lines 14–20 deals all 52 Cards in the deck and displays them in four columns of 13 Cards each. Line 16 deals and displays a Card object by invoking myDeckOfCards’s DealCard method. When Console.Write outputs a Card with string formatting, the Card’s ToString method (declared in lines 16–19 of Fig. 8.9) is invoked implicitly. Because the field width is negative, the result is output left justified in a field of width 19.

Example 8.11. Card shuffling and dealing application.

 1   // Fig. 8.11: DeckOfCardsTest.cs
 2   // Card shuffling and dealing application.
 3   using System;
 4
 5   public class DeckOfCardsTest
 6   {
 7      // execute application
 8      public static void Main( string[] args )
 9      {
10         DeckOfCards myDeckOfCards = new DeckOfCards();
11         myDeckOfCards.Shuffle(); // place Cards in random order
12
13         // display all 52 Cards in the order in which they are dealt
14         for ( int i = 0; i < 52; i++ )
15         {
16            Console.Write( "{0,-19}", myDeckOfCards.DealCard() );
17
18            if ( ( i + 1 ) % 4 == 0 )
19               Console.WriteLine();
20         } // end for
21      } // end Main
22   } // end class DeckOfCardsTest
Eight of Clubs     Ten of Clubs       Ten of Spades      Four of Spades
Ace of Spades      Jack of Spades     Three of Spades    Seven of Spades
Three of Diamonds  Five of Clubs      Eight of Spades    Five of Hearts
Ace of Hearts      Ten of Hearts      Deuce of Hearts    Deuce of Clubs
Jack of Hearts     Nine of Spades     Four of Hearts     Seven of Clubs
Queen of Spades    Seven of Diamonds  Five of Diamonds   Ace of Clubs
Four of Clubs      Ten of Diamonds    Jack of Clubs      Six of Diamonds
Eight of Diamonds  King of Hearts     Three of Clubs     King of Spades
King of Diamonds   Six of Spades      Deuce of Spades    Five of Spades
Queen of Clubs     King of Clubs      Queen of Hearts    Seven of Hearts
Ace of Diamonds    Deuce of Diamonds  Four of Diamonds   Nine of Clubs
Queen of Diamonds  Jack of Diamonds   Six of Hearts      Nine of Diamonds
Nine of Hearts     Three of Hearts    Six of Clubs       Eight of Hearts

foreach Statement

In previous examples, we demonstrated how to use counter-controlled for statements to iterate through the elements in an array. In this section, we introduce the foreach statement, which iterates through the elements of an entire array or collection. This section discusses how to use the foreach statement to loop through an array. We show how to use it with collections in Chapter 23. The syntax of a foreach statement is:

foreach ( type identifier in arrayName )
   statement

where type and identifier are the type and name (e.g., int number) of the iteration variable, and arrayName is the array through which to iterate. The type of the iteration variable must be consistent with the type of the elements in the array. As the next example illustrates, the iteration variable represents successive values in the array on successive iterations of the foreach statement.

Figure 8.12 uses the foreach statement (lines 13–14) to calculate the sum of the integers in an array of student grades. The type specified is int, because array contains int values—therefore, the loop will select one int value from the array during each iteration. The foreach statement iterates through successive values in the array one by one. The foreach header can be read concisely as “for each iteration, assign the next element of array to int variable number, then execute the following statement.” Thus, for each iteration, identifier number represents the next int value in the array. Lines 13–14 are equivalent to the following counter-controlled repetition used in lines 13–14 of Fig. 8.5 to total the integers in array:

for ( int counter = 0; counter < array.Length; counter++ )
   total += array[ counter ];

Example 8.12. Using the foreach statement to total integers in an array.

 1   // Fig. 8.12: ForEachTest.cs
 2   // Using the foreach statement to total integers in an array.
 3   using System;
 4
 5   public class ForEachTest
 6   {
 7      public static void Main( string[] args )
 8      {
 9         int[] array = { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
10         int total = 0;
11
12         // add each element's value to total
13         foreach ( int number in array )     
14            total += number;                 
15
16         Console.WriteLine( "Total of array elements: {0}", total );
17      } // end Main
18   } // end class ForEachTest

Total of array elements: 849

Common Programming Error 8.4

Common Programming Error 8.4

The foreach statement can be used only to access array elements—it cannot be used to modify elements. Any attempt to change the value of the iteration variable in the body of a foreach statement will cause a compilation error.

The foreach statement can be used in place of the for statement whenever code looping through an array does not require access to the counter indicating the index of the current array element. For example, totaling the integers in an array requires access only to the element values—the index of each element is irrelevant. However, if an application must use a counter for some reason other than simply to loop through an array (e.g., to display an index number next to each array element value, as in the examples earlier in this chapter), use the for statement.

Passing Arrays and Array Elements to Methods

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

double[] hourlyTemperatures = new double[ 24 ];

then the method call

ModifyArray( hourlyTemperatures );

passes the reference of array hourlyTemperatures to method ModifyArray. Every array object “knows” its own length (and makes it available via its Length property). Thus, when we pass an array object’s reference to a method, we need not pass the array length as an additional argument.

For a method to receive an array reference through a method call, the method’s parameter list must specify an array parameter. For example, the method header for method ModifyArray might be written as

void ModifyArray( double[] b )

indicating that ModifyArray receives the reference of an array of doubles in parameter b. The method call passes array hourlyTemperature’s reference, so when the called method uses the array variable b, it refers to the same array object as hourlyTemperatures in the calling method.

When an argument to a method is an entire array or an individual array element of a reference type, the called method receives a copy of the reference. However, when an argument to a method is an individual array element of a value type, the called method receives a copy of the element’s value. To pass an individual array element to a method, use the indexed name of the array as an argument in the method call. If you want to pass a value-type array element to a method by reference, you must use the ref keyword as shown in Section 7.16.

Figure 8.13 demonstrates the difference between passing an entire array and passing a value-type array element to a method. The foreach statement at lines 17–18 outputs the five elements of array (an array of int values). Line 20 invokes method ModifyArray, passing array as an argument. Method ModifyArray (lines 37–41) receives a copy of array’s reference and uses the reference to multiply each of array’s elements by 2. To prove that array’s elements (in Main) were modified, the foreach statement at lines 24–25 outputs the five elements of array again. As the output shows, method ModifyArray doubled the value of each element.

Example 8.13. Passing arrays and individual array elements to methods.

 1   // Fig. 8.13: PassArray.cs
 2   // Passing arrays and individual array elements to methods.
 3   using System;
 4
 5   public class PassArray
 6   {
 7      // Main creates array and calls ModifyArray and ModifyElement
 8      public static void Main( string[] args )
 9      {
10         int[] array = { 1, 2, 3, 4, 5 };
11
12         Console.WriteLine(
13            "Effects of passing reference to entire array:
" +
14            "The values of the original array are:" );
15
16         // output original array elements
17         foreach ( int value in array )
18            Console.Write( "   {0}", value );
19
20         ModifyArray( array ); // pass array reference
21         Console.WriteLine( "

The values of the modified array are:" );
22
23         // output modified array elements
24         foreach ( int value in array )
25            Console.Write( "   {0}", value );
26
27         Console.WriteLine(
28            "

Effects of passing array element value:
" +
29            "array[3] before ModifyElement: {0}", array[ 3 ] );
30
31         ModifyElement( array[ 3 ] ); // attempt to modify array[ 3 ]
32         Console.WriteLine(
33            "array[3] after ModifyElement: {0}", array[ 3 ] );
34      } // end Main
35
36      // multiply each element of an array by 2                     
37      public static void ModifyArray( int[] array2 )                
38      {                                                             
39         for ( int counter = 0; counter < array2.Length; counter++ )
40            array2[ counter ] *= 2;                                 
41      } // end method ModifyArray                                   
42
43      // multiply argument by 2                                 
44      public static void ModifyElement( int element )           
45      {                                                         
46         element *= 2;                                          
47         Console.WriteLine(                                     
48            "Value of element in ModifyElement: {0}", element );
49      } // end method ModifyElement                             
50   } // end class PassArray

Effects of passing reference to entire array:
The values of the original array are:
   1   2   3   4   5

The values of the modified array are:
   2   4   6   8   10

Effects of passing array element value:
array[3] before ModifyElement: 8
Value of element in ModifyElement: 16
array[3] after ModifyElement: 8

Figure 8.13 next demonstrates that when a copy of an individual value-type array element is passed to a method, modifying the copy in the called method does not affect the original value of that element in the calling method’s array. To show the value of array[3] before invoking method ModifyElement, lines 27–29 output the value of array[3], which is 8. Line 31 calls method ModifyElement and passes array[3] as an argument. Remember that array[3] is actually one int value (8) in array. Therefore, the application passes a copy of the value of array[3]. Method ModifyElement (lines 44–49) multiplies the value received as an argument by 2, stores the result in its parameter element, then outputs the value of element (16). Since method parameters, like local variables, cease to exist when the method in which they’re declared completes execution, the method parameter element is destroyed when method ModifyElement terminates. Thus, when the application returns control to Main, lines 32–33 output the unmodified value of array[3] (i.e., 8).

Passing Arrays by Value and by Reference

In C#, a variable that “stores” an object, such as an array, does not actually store the object itself. Instead, such a variable stores a reference to the object. The distinction between reference-type variables and value-type variables raises some subtle issues that you must understand to create secure, stable programs.

As you know, when an application passes an argument to a method, the called method receives a copy of that argument’s value. Changes to the local copy in the called method do not affect the original variable in the caller. If the argument is of a reference type, the method makes a copy of the reference, not a copy of the actual object that’s referenced. The local copy of the reference also refers to the original object, which means that changes to the object in the called method affect the original object.

Performance Tip 8.1

Performance Tip 8.1

Passing references to arrays and other objects 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 waste time and consume considerable storage for the copies of the arrays.

In Section 7.16, you learned that C# allows variables to be passed by reference with keyword ref. You can also use keyword ref to pass a reference-type variable by reference, which allows the called method to modify the original variable in the caller and make that variable refer to a different object. This is a subtle capability, which, if misused, can lead to problems. For instance, when a reference-type object like an array is passed with ref, the called method actually gains control over the reference itself, allowing the called method to replace the original reference in the caller with a reference to a different object, or even with null. Such behavior can lead to unpredictable effects, which can be disastrous in mission-critical applications. The application in Fig. 8.14 demonstrates the subtle difference between passing a reference by value and passing a reference by reference with keyword ref.

Example 8.14. Passing an array reference by value and by reference.

 1   // Fig. 8.14: ArrayReferenceTest.cs
 2   // Testing the effects of passing array references
 3   // by value and by reference.
 4   using System;
 5
 6   public class ArrayReferenceTest
 7   {
 8      public static void Main( string[] args )
 9      {
10         // create and initialize firstArray
11         int[] firstArray = { 1, 2, 3 };
12
13         // copy the reference in variable firstArray
14         int[] firstArrayCopy = firstArray;
15
16         Console.WriteLine(
17            "Test passing firstArray reference by value" );
18
19         Console.Write( "
Contents of firstArray " +
20            "before calling FirstDouble:
	" );
21
22         // display contents of firstArray
23         for ( int i = 0; i < firstArray.Length; i++ )
24            Console.Write( "{0} ", firstArray[ i ] );
25
26         // pass variable firstArray by value to FirstDouble
27         FirstDouble( firstArray );                         
28
29         Console.Write( "

Contents of firstArray after " +
30            "calling FirstDouble
	" );
31
32         // display contents of firstArray
33         for ( int i = 0; i < firstArray.Length; i++ )
34            Console.Write( "{0} ", firstArray[ i ] );
35
36         // test whether reference was changed by FirstDouble
37         if ( firstArray == firstArrayCopy )
38            Console.WriteLine(
39               "

The references refer to the same array" );
40         else
41            Console.WriteLine(
42               "

The references refer to different arrays" );
43
44         // create and initialize secondArray
45         int[] secondArray = { 1, 2, 3 };
46
47         // copy the reference in variable secondArray
48         int[] secondArrayCopy = secondArray;
49
50         Console.WriteLine( "
Test passing secondArray " +
51            "reference by reference" );
52
53         Console.Write( "
Contents of secondArray " +
54            "before calling SecondDouble:
	" );
55
56         // display contents of secondArray before method call
57         for ( int i = 0; i < secondArray.Length; i++ )
58            Console.Write( "{0} ", secondArray[ i ] );
59
60         // pass variable secondArray by reference to SecondDouble
61         SecondDouble( ref secondArray );                         
62
63         Console.Write( "

Contents of secondArray " +
64            "after calling SecondDouble:
	" );
65
66         // display contents of secondArray after method call
67         for ( int i = 0; i < secondArray.Length; i++ )
68            Console.Write( "{0} ", secondArray[ i ] );
69
70         // test whether reference was changed by SecondDouble
71         if ( secondArray == secondArrayCopy )
72            Console.WriteLine(
73               "

The references refer to the same array" );
74         else
75            Console.WriteLine(
76               "

The references refer to different arrays" );
77      } // end Main
78
79      // modify elements of array and attempt to modify reference
80      public static void FirstDouble( int[] array )
81      {
82         // double each element's value
83         for ( int i = 0; i < array.Length; i++ )
84            array[ i ] *= 2;
85
86         // create new object and assign its reference to array
87         array = new int[] { 11, 12, 13 };                     
88      } // end method FirstDouble
89
90      // modify elements of array and change reference array
91      // to refer to a new array
92      public static void SecondDouble( ref int[] array )
93      {
94         // double each element's value
95         for ( int i = 0; i < array.Length; i++ )
96             array[ i ] *= 2;
97
98         // create new object and assign its reference to array
99         array = new int[] { 11, 12, 13 };                     
100     } // end method SecondDouble
101  } // end class ArrayReferenceTest
Test passing firstArray reference by value

Contents of firstArray before calling FirstDouble:
         1 2 3

Contents of firstArray after calling FirstDouble
         2 4 6

The references refer to the same array

Test passing secondArray reference by reference

Contents of secondArray before calling SecondDouble:
         1 2 3

Contents of secondArray after calling SecondDouble:
         11 12 13

The references refer to different arrays

Lines 11 and 14 declare two integer array variables, firstArray and firstArrayCopy. Line 11 initializes firstArray with the values 1, 2 and 3. The assignment statement at line 14 copies the reference stored in firstArray to variable firstArrayCopy, causing these variables to reference the same array object. We make the copy of the reference so that we can determine later whether reference firstArray gets overwritten. The for statement at lines 23–24 displays the contents of firstArray before it’s passed to method FirstDouble (line 27) so that we can verify that the called method indeed changes the array’s contents.

The for statement in method FirstDouble (lines 83–84) multiplies the values of all the elements in the array by 2. Line 87 creates a new array containing the values 11, 12 and 13, and assigns the array’s reference to parameter array in an attempt to overwrite reference firstArray in the caller—this, of course, does not happen, because the reference was passed by value. After method FirstDouble executes, the for statement at lines 33–34 displays the contents of firstArray, demonstrating that the values of the elements have been changed by the method. The if...else statement at lines 37–42 uses the == operator to compare references firstArray (which we just attempted to overwrite) and firstArrayCopy. The expression in line 37 evaluates to true if the operands of operator == reference the same object. In this case, the object represented by firstArray is the array created in line 11—not the array created in method FirstDouble (line 87)—so the original reference stored in firstArray was not modified.

Lines 45–76 perform similar tests, using array variables secondArray and secondArrayCopy, and method SecondDouble (lines 92–100). Method SecondDouble performs the same operations as FirstDouble, but receives its array argument using keyword ref. In this case, the reference stored in secondArray after the method call is a reference to the array created in line 99 of SecondDouble, demonstrating that a variable passed with keyword ref can be modified by the called method so that the variable in the caller actually points to a different object—in this case, an array created in SecondDouble. The if...else statement in lines 71–76 confirms that secondArray and secondArrayCopy no longer refer to the same array.

Software Engineering Observation 8.1

Software Engineering Observation 8.1

When a method receives a reference-type parameter by value, a copy of the object’s reference is passed. This prevents a method from overwriting references passed to that method. In the vast majority of cases, protecting the caller’s reference from modification is the desired behavior. If you encounter a situation where you truly want the called procedure to modify the caller’s reference, pass the reference-type parameter using keyword ref—but, again, such situations are rare.

Software Engineering Observation 8.2

Software Engineering Observation 8.2

In C#, references to objects (including arrays) are passed to called methods. A called method receiving a reference to an object in a caller can interact with, and possibly change, the caller’s object.

Case Study: Class GradeBook Using an Array to Store Grades

This section further evolves class GradeBook, introduced in Chapter 4 and expanded in Chapters 56. Recall that this class represents a grade book used by an instructor to store and analyze a set of student grades. Previous versions of the class process a set of grades entered by the user, but do not maintain the individual grade values in instance variables of the class. Thus, repeat calculations require the user to re-enter the same grades. One way to solve this problem would be to store each grade entered in an individual instance of the class. For example, we could create instance variables 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, and the class would not be able to process any more than 10 grades at a time. 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 (Fig. 8.15) 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. Variable grades (which will refer to an array of ints) is declared as an instance variable in line 7—therefore, each GradeBook object maintains its own set of grades. The class’s constructor (lines 14–18) has two parameters—the name of the course and an array of grades. When an application (e.g., class GradeBookTest in Fig. 8.16) creates a GradeBook object, the application passes an existing int array to the constructor, which assigns the array’s reference to instance variable grades (line 17). The size of array grades is determined by the class that passes the array to the constructor.

Example 8.15. Grade book using an array to store test grades.

 1   // Fig. 8.15: GradeBook.cs
 2   // Grade book using an array to store test grades.
 3   using System;
 4
 5   public class GradeBook
 6   {
 7      private int[] grades; // array of student grades
 8
 9      // auto-implemented property CourseName
10      public string CourseName { get; set; }
11
12      // two-parameter constructor initializes
13      // auto-implemented property CourseName and grades array
14      public GradeBook( string name, int[] gradesArray )
15      {
16         CourseName = name; // set CourseName to name
17         grades = gradesArray; // initialize grades array
18      } // end two-parameter GradeBook constructor
19
20      // display a welcome message to the GradeBook user
21      public void DisplayMessage()
22      {
23         // auto-implemented property CourseName gets the name of course
24         Console.WriteLine( "Welcome to the grade book for
{0}!
",
25            CourseName );
26      } // end method DisplayMessage
27
28      // perform various operations on the data
29      public void ProcessGrades()
30      {
31         // output grades array
32         OutputGrades();
33
34         // call method GetAverage to calculate the average grade
35         Console.WriteLine( "
Class average is {0:F}", GetAverage() );
36
37         // call methods GetMinimum and GetMaximum
38         Console.WriteLine( "Lowest grade is {0}
Highest grade is {1}
",
39            GetMinimum(), GetMaximum() );
40
41         // call OutputBarChart to display grade distribution chart
42         OutputBarChart();
43      } // end method ProcessGrades
44
45      // find minimum grade
46      public int GetMinimum()
47      {
48         int lowGrade = grades[ 0 ]; // assume grades[ 0 ] is smallest
49
50         // loop through grades array                             
51         foreach ( int grade in grades )                          
52         {                                                        
53            // if grade lower than lowGrade, assign it to lowGrade
54            if ( grade < lowGrade )                               
55               lowGrade = grade; // new lowest grade              
56         } // end for                                             
57
58         return lowGrade; // return lowest grade
59      } // end method GetMinimum
60
61      // find maximum grade
62      public int GetMaximum()
63      {
64         int highGrade = grades[ 0 ]; // assume grades[ 0 ] is largest
65
66         // loop through grades array
67         foreach ( int grade in grades )
68         {
69            // if grade greater than highGrade, assign it to highGrade
70            if ( grade > highGrade )
71               highGrade = grade; // new highest grade
72         } // end for
73
74         return highGrade; // return highest grade
75      } // end method GetMaximum
76
77      // determine average grade for test
78      public double GetAverage()
79      {
80         int total = 0; // initialize total
81
82         // sum grades for one student  
83         foreach ( int grade in grades )
84            total += grade;             
85
86         // return average of grades
87         return ( double ) total / grades.Length;
88      } // end method GetAverage
89
90      // output bar chart displaying grade distribution
91      public void OutputBarChart()
92      {
93         Console.WriteLine( "Grade distribution:" );
94
95         // stores frequency of grades in each range of 10 grades
96         int[] frequency = new int[ 11 ];
97
98         // for each grade, increment the appropriate frequency
99         foreach ( int grade in grades )                       
100           ++frequency[ grade / 10 ];                         
101
102        // for each grade frequency, display bar in chart
103        for ( int count = 0; count < frequency.Length; count++ )
104        {
105           // output bar label ( "00-09: ", ..., "90-99: ", "100: " )
106           if ( count == 10 )
107              Console.Write( " 100: " );
108           else
109              Console.Write( "{0:D2}-{1:D2}: ",
110                 count * 10, count * 10 + 9 );
111
112           // display bar of asterisks
113           for ( int stars = 0; stars < frequency[ count ]; stars++ )
114              Console.Write( "*" );
115
116           Console.WriteLine(); // start a new line of output
117        } // end outer for
118     } // end method OutputBarChart
119
120     // output the contents of the grades array
121     public void OutputGrades()
122     {
123        Console.WriteLine( "The grades are:
" );
124
125        // output each student's grade                             
126        for ( int student = 0; student < grades.Length; student++ )
127           Console.WriteLine( "Student {0,2}: {1,3}",              
128              student + 1, grades[ student ] );                    
129     } // end method OutputGrades
130  } // end class GradeBook

Thus, a GradeBook object can process a variable number of grades—as many as are in the array in the caller. The grade values in the passed array could have been input from a user at the keyboard or read from a file on disk (as discussed in Chapter 17). In our test application, we simply initialize an array with a set of grade values (Fig. 8.16, line 9). Once the grades are stored in instance variable grades of class GradeBook, all the class’s methods can access the elements of grades as needed to perform various calculations.

Example 8.16. Create a GradeBook object using an array of grades.

 1   // Fig. 8.16: GradeBookTest.cs
 2   // Create GradeBook object using an array of grades.
 3   public class GradeBookTest
 4   {
 5      // Main method begins application execution
 6      public static void Main( string[] args )
 7      {
 8         // one-dimensional array of student grades                      
 9         int[] gradesArray = { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
10
11         GradeBook myGradeBook = new GradeBook(
12            "CS101 Introduction to C# Programming", gradesArray );
13         myGradeBook.DisplayMessage();
14         myGradeBook.ProcessGrades();
15      } // end Main
16   } // end class GradeBookTest

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:
00-09:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
  100: *

Method ProcessGrades (lines 29–43) contains a series of method calls that result in the output of a report summarizing the grades. Line 32 calls method OutputGrades to display the contents of array grades. Lines 126–128 in method OutputGrades use a for statement to output the student grades. A for statement, rather than a foreach, must be used in this case, because lines 127–128 use counter variable student’s value to output each grade next to a particular student number (see Fig. 8.16). Although array indices start at 0, an instructor would typically number students starting at 1. Thus, lines 127–128 output student + 1 as the student number to produce grade labels "Student 1: ", "Student 2: " and so on.

Method ProcessGrades next calls method GetAverage (line 35) to obtain the average of the grades in the array. Method GetAverage (lines 78–88) uses a foreach statement to total the values in array grades before calculating the average. The iteration variable in the foreach’s header (e.g., int grade) indicates that for each iteration, int variable grade takes on a value in array grades. The averaging calculation in line 87 uses grades.Length to determine the number of grades being averaged.

Lines 38–39 in method ProcessGrades call methods GetMinimum and GetMaximum to determine the lowest and highest grades of any student on the exam, respectively. Each of these methods uses a foreach statement to loop through array grades. Lines 51–56 in method GetMinimum loop through the array, and lines 54–55 compare each grade to lowGrade. If a grade is less than lowGrade, lowGrade is set to that grade. When line 58 executes, lowGrade contains the lowest grade in the array. Method GetMaximum (lines 62–75) works the same way as method GetMinimum.

Finally, line 42 in method ProcessGrades calls method OutputBarChart to display a distribution chart of the grade data, using a technique similar to that in Fig. 8.6. 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 99–100 use a technique similar to that in Figs. 8.7 and 8.8 to calculate the frequency of grades in each category. Line 96 declares variable frequency and initializes it with an array of 11 ints to store the frequency of grades in each grade category. For each grade in array grades, lines 99–100 increment the appropriate element of the frequency array. To determine which element to increment, line 100 divides the current grade by 10, using integer division. For example, if grade is 85, line 100 increments frequency[8] to update the count of grades in the range 80–89. Lines 103–117 next display the bar chart (see Fig. 8.6) based on the values in array frequency. Like lines 24–25 of Fig. 8.6, lines 113–114 of Fig. 8.15 use a value in array frequency to determine the number of asterisks to display in each bar.

Class GradeBookTest That Demonstrates Class GradeBook

The application of Fig. 8.16 creates an object of class GradeBook (Fig. 8.15) using int array gradesArray (declared and initialized in line 9). Lines 11–12 pass a course name and gradesArray to the GradeBook constructor. Line 13 displays a welcome message, and line 14 invokes the GradeBook object’s ProcessGrades method. The output reveals the summary of the 10 grades in myGradeBook.

Software Engineering Observation 8.3

Software Engineering Observation 8.3

A test harness (or test application) is responsible for creating an object of the class being tested and providing it with data. This data could come from any of several sources. Test data can be placed directly into an array with an array initializer, it can come from the user at the keyboard or it can come from a file (as you’ll see in Chapter 17). After passing this data to the class’s constructor to instantiate the object, the test harness should call the object to test its methods and manipulate its data. Gathering data in the test harness like this allows the class to manipulate data from several sources.

Multidimensional Arrays

Multidimensional arrays with two dimensions are often used to represent tables of values consisting of information arranged in rows and columns. To identify a particular table element, we must specify two indices. By convention, the first identifies the element’s row and the second its column. Arrays that require two indices to identify a particular element are called two-dimensional arrays. (Multidimensional arrays can have more than two dimensions, but such arrays are beyond the scope of this book.) C# supports two types of two-dimensional arrays—rectangular arrays and jagged arrays.

Rectangular Arrays

Rectangular arrays are used to represent tables of information in the form of rows and columns, where each row has the same number of columns. Figure 8.17 illustrates a rectangular array named a containing three rows and four columns—a three-by-four array. In general, an array with m rows and n columns is called an m-by-narray.

Every element in array a is identified in Fig. 8.17 by an array-access expression of the form a[row, column]; a is the name of the array, and row and column are the indices that uniquely identify each element in array a by row and column number. The names of the elements in row 0 all have a first index of 0, and the names of the elements in column 3 all have a second index of 3.

Rectangular array with three rows and four columns.

Figure 8.17. Rectangular array with three rows and four columns.

Like one-dimensional arrays, multidimensional arrays can be initialized with array initializers in declarations. A rectangular array b with two rows and two columns could be declared and initialized with nested array initializers as follows:

int[ , ] b = { { 1, 2 }, { 3, 4 } };

The initializer values are grouped by row in braces. So 1 and 2 initialize b[0, 0] and b[0, 1], respectively, and 3 and 4 initialize b[1, 0] and b[1, 1], respectively. The compiler counts the number of nested array initializers (represented by sets of two inner braces within the outer braces) in the initializer list to determine the number of rows in array b. The compiler counts the initializer values in the nested array initializer for a row to determine the number of columns (two) in that row. The compiler will generate an error if the number of initializers in each row is not the same, because every row of a rectangular array must have the same length.

Jagged Arrays

A jagged array is maintained as a one-dimensional array in which each element refers to a one-dimensional array. The manner in which jagged arrays are represented makes them quite flexible, because the lengths of the rows in the array need not be the same. For example, jagged arrays could be used to store a single student’s exam grades across multiple classes, where the number of exams may vary from class to class.

We can access the elements in a jagged array by an array-access expression of the form arrayName[row][column]—similar to the array-access expression for rectangular arrays, but with a separate set of square brackets for each dimension. A jagged array with three rows of different lengths could be declared and initialized as follows:

int[][] jagged = { new int[] { 1, 2 },
                   new int[] { 3 },
                   new int[] { 4, 5, 6 } };

In this statement, 1 and 2 initialize jagged[0][0] and jagged[0][1], respectively; 3 initializes jagged[1][0]; and 4, 5 and 6 initialize jagged[2][0], jagged[2][1] and jagged[2][2], respectively. Therefore, array jagged in the preceding declaration is actually composed of four separate one-dimensional arrays—one that represents the rows, one containing the values in the first row ({1, 2}), one containing the value in the second row ({3}) and one containing the values in the third row ({4, 5, 6}). Thus, array jagged itself is an array of three elements, each a reference to a one-dimensional array of int values.

Observe the differences between the array-creation expressions for rectangular arrays and for jagged arrays. Two sets of square brackets follow the type of jagged, indicating that this is an array of int arrays. Furthermore, in the array initializer, C# requires the keyword new to create an array object for each row. Figure 8.18 illustrates the array reference jagged after it’s been declared and initialized.

Jagged array with three rows of different lengths.

Figure 8.18. Jagged array with three rows of different lengths.

Creating Two-Dimensional Arrays with Array-Creation Expressions

A rectangular array can be created with an array-creation expression. For example, the following lines declare variable b and assign it a reference to a three-by-four rectangular array:

int[ , ] b;
b = new int[ 3, 4 ];

In this case, we use the literal values 3 and 4 to specify the number of rows and number of columns, respectively, but this is not required—applications can also use variables and expressions to specify array dimensions. As with one-dimensional arrays, the elements of a rectangular array are initialized when the array object is created.

A jagged array cannot be completely created with a single array-creation expression. The following statement is a syntax error:

int[][] c = new int[ 2 ][ 5 ]; // error

Instead, each one-dimensional array in the jagged array must be initialized separately. A jagged array can be created as follows:

int[][] c;
c = new int[ 2 ][ ]; // create 2 rows
c[ 0 ] = new int[ 5 ]; // create 5 columns for row 0
c[ 1 ] = new int[ 3 ]; // create 3 columns for row 1

The preceding statements create a jagged array with two rows. Row 0 has five columns, and row 1 has three columns.

Two-Dimensional Array Example: Displaying Element Values

Figure 8.19 demonstrates initializing rectangular and jagged arrays with array initializers and using nested for loops to traverse the arrays (i.e., visit every element of each array).

Example 8.19. Initializing jagged and rectangular arrays.

 1   // Fig. 8.19: InitArray.cs
 2   // Initializing rectangular and jagged arrays.
 3   using System;
 4
 5   public class InitArray
 6   {
 7      // create and output rectangular and jagged arrays
 8      public static void Main( string[] args )
 9      {
10         // with rectangular arrays,
11         // every row must be the same length.
12         int[ , ] rectangular = { { 1, 2, 3 }, { 4, 5, 6 } };
13
14         // with jagged arrays,
15         // we need to use "new int[]" for every row,
16         // but every row does not need to be the same length.
17         int[][] jagged = { new int[] { 1, 2 },     
18                            new int[] { 3 },        
19                            new int[] { 4, 5, 6 } };
20
21         OutputArray( rectangular ); // displays array rectangular by row
22         Console.WriteLine(); // output a blank line
23         OutputArray( jagged ); // displays array jagged by row
24      } // end Main
25
26      // output rows and columns of a rectangular array
27      public static void OutputArray( int[ , ] array )
28      {
29         Console.WriteLine( "Values in the rectangular array by row are" );
30
31         // loop through array's rows                                      
32         for ( int row = 0; row < array.GetLength( 0 ); row++ )            
33         {                                                                 
34            // loop through columns of current row                         
35            for ( int column = 0; column < array.GetLength( 1 ); column++ )
36               Console.Write( "{0}  ", array[ row, column ] );              
37                                                                           
38            Console.WriteLine(); // start new line of output               
39         } // end outer for                                                
40      } // end method OutputArray
41
42      // output rows and columns of a jagged array
43      public static void OutputArray( int[][] array )
44      {
45         Console.WriteLine( "Values in the jagged array by row are" );
46
47         // loop through each row                           
48         foreach ( int[] row in array )                     
49         {                                                  
50            // loop through each element in current row     
51            foreach ( int element in row )                  
52               Console.Write( "{0}  ", element );           
53                                                            
54            Console.WriteLine(); // start new line of output
55         } // end outer foreach                             
56      } // end method OutputArray
57   } // end class InitArray

Values in the rectangular array by row are
1  2  3
4  5  6

Values in the jagged array by row are
1  2
3
4  5  6

Class InitArray’s Main method creates two arrays. Line 12 uses nested array initializers to initialize variable rectangular with an array in which row 0 has the values 1, 2 and 3, and row 1 has the values 4, 5 and 6. Lines 17–19 uses nested initializers of different lengths to initialize variable jagged. In this case, the initializer uses the keyword new to create a one-dimensional array for each row. Row 0 is initialized to have two elements with values 1 and 2, respectively. Row 1 is initialized to have one element with value 3. Row 2 is initialized to have three elements with the values 4, 5 and 6, respectively.

Method OutputArray has been overloaded with two versions. The first version (lines 27–40) specifies the array parameter as int[,] array to indicate that it takes a rectangular array. The second version (lines 43–56) takes a jagged array, because its array parameter is listed as int[][] array.

Line 21 invokes method OutputArray with argument rectangular, so the version of OutputArray at lines 27–40 is called. The nested for statement (lines 32–39) outputs the rows of a rectangular array. The loop-continuation condition of each for statement (lines 32 and 35) uses the rectangular array’s GetLength method to obtain the length of each dimension. Dimensions are numbered starting from 0, so the method call GetLength(0) on array returns the size of the first dimension of the array (the number of rows), and the call GetLength(1) returns the size of the second dimension (the number of columns).

Line 23 invokes method OutputArray with argument jagged, so the version of OutputArray at lines 43–56 is called. The nested foreach statement (lines 48–55) outputs the rows of a jagged array. The inner foreach statement (lines 51–52) iterates through each element in the current row of the array. This allows the loop to determine the exact number of columns in each row. Since the jagged array is created as an array of arrays, we can use nested foreach statements to output the elements in the console window. The outer loop iterates through the elements of array, which are references to one-dimensional arrays of int values that represent each row. The inner loop iterates through the elements of the current row. A foreach statement can also iterate through all the elements in a rectangular array. In this case, foreach iterates through all the rows and columns starting from row 0, as if the elements were in a one-dimensional array.

Common Multidimensional-Array Manipulations Performed with for Statements

Many common array manipulations use for statements. As an example, the following for statement sets all the elements in row 2 of rectangular array a in Fig. 8.17 to 0:

for ( int column = 0; column < a.GetLength( 1 ); column++)
   a[ 2, column ] = 0;

We specified row 2; therefore, we know that the first index is always 2 (0 is the first row, and 1 is the second row). This for loop varies only the second index (i.e., the column index). The preceding for statement is equivalent to the assignment statements

a[ 2, 0 ] = 0;
a[ 2, 1 ] = 0;
a[ 2, 2 ] = 0;
a[ 2, 3 ] = 0;

The following nested for statement totals the values of all the elements in array a:

int total = 0;

for ( int row = 0; row < a.GetLength( 0 ); row++ )
{
   for ( int column = 0; column < a.GetLength( 1 ); column++ )
      total += a[ row, column ];
} // end outer for

These nested for statements total the array elements one row at a time. The outer for statement begins by setting the row index to 0 so that row 0’s elements can be totaled by the inner for statement. The outer for then increments row to 1 so that row 1’s elements can be totaled. Then the outer for increments row to 2 so that row 2’s elements can be totaled. The variable total can be displayed when the outer for statement terminates. In the next example, we show how to process a rectangular array in a more concise manner using foreach statements.

Case Study: Class GradeBook Using a Rectangular Array

In Section 8.9, we presented class GradeBook (Fig. 8.15), which used a one-dimensional array to store student grades on a single exam. In most courses, students take several exams. Instructors are likely to want to analyze grades across the entire course, both for a single student and for the class as a whole.

Storing Student Grades in a Rectangular Array in Class GradeBook

Figure 8.20 contains a version of class GradeBook that uses a rectangular array grades to store the grades of a number of students on multiple exams. Each row of the array represents a single student’s grades for the entire course, and each column represents the grades for the whole class on one of the exams the students took during the course. An application such as GradeBookTest (Fig. 8.21) passes the array as an argument to the GradeBook constructor. In this example, we use a 10-by-3 array containing 10 students’ grades on three exams. Five methods perform array manipulations to process the grades. Each method is similar to its counterpart in the earlier one-dimensional-array version of class GradeBook (Fig. 8.15). Method GetMinimum (lines 44–58) determines the lowest grade of any student for the semester. Method GetMaximum (lines 61–75) determines the highest grade of any student for the semester. Method GetAverage (lines 78–90) determines a particular student’s semester average. Method OutputBarChart (lines 93–122) outputs a bar chart of the distribution of all student grades for the semester. Method OutputGrades (lines 125–149) outputs the two-dimensional array in tabular format, along with each student’s semester average.

Example 8.20. Grade book using a rectangular array to store grades.

 1   // Fig. 8.20: GradeBook.cs
 2   // Grade book using rectangular array to store grades.
 3   using System;
 4
 5   public class GradeBook
 6   {
 7      private int[ , ] grades; // rectangular array of student grades
 8
 9      // auto-implemented property CourseName
10      public string CourseName { get; set; }
11
12      // two-parameter constructor initializes
13      // auto-implemented property CourseName and grades array
14      public GradeBook( string name, int[ , ] gradesArray )
15      {
16         CourseName = name; // set CourseName to name
17         grades = gradesArray; // initialize grades array
18      } // end two-parameter GradeBook constructor
19
20      // display a welcome message to the GradeBook user
21      public void DisplayMessage()
22      {
23         // auto-implemented property CourseName gets the name of course
24         Console.WriteLine( "Welcome to the grade book for
{0}!
",
25            CourseName );
26      } // end method DisplayMessage
27
28      // perform various operations on the data
29      public void ProcessGrades()
30      {
31         // output grades array
32         OutputGrades();
33
34         // call methods GetMinimum and GetMaximum
35         Console.WriteLine( "
{0} {1}
{2} {3}
",
36            "Lowest grade in the grade book is", GetMinimum(),
37            "Highest grade in the grade book is", GetMaximum() );
38
39         // output grade distribution chart of all grades on all tests
40         OutputBarChart();
41      } // end method ProcessGrades
42
43      // find minimum grade
44      public int GetMinimum()
45      {
46         // assume first element of grades array is smallest
47         int lowGrade = grades[ 0, 0 ];
48
49         // loop through elements of rectangular grades array    
50         foreach ( int grade in grades )                         
51         {                                                       
52            // if grade less than lowGrade, assign it to lowGrade
53            if ( grade < lowGrade )                              
54               lowGrade = grade;                                 
55         } // end foreach                                        
56
57         return lowGrade; // return lowest grade
58      } // end method GetMinimum
59
60      // find maximum grade
61      public int GetMaximum()
62      {
63         // assume first element of grades array is largest
64         int highGrade = grades[ 0, 0 ];
65
66         // loop through elements of rectangular grades array
67         foreach ( int grade in grades )
68         {
69            // if grade greater than highGrade, assign it to highGrade
70            if ( grade > highGrade )
71               highGrade = grade;
72         } // end foreach
73
74         return highGrade; // return highest grade
75      } // end method GetMaximum
76
77      // determine average grade for particular student
78      public double GetAverage( int student )          
79      {                                                
80         // get the number of grades per student       
81         int amount = grades.GetLength( 1 );           
82         int total = 0; // initialize total            
83                                                       
84         // sum grades for one student                 
85         for ( int exam = 0; exam < amount; exam++ )   
86            total += grades[ student, exam ];          
87                                                       
88         // return average of grades                   
89         return ( double ) total / amount;             
90      } // end method GetAverage                       
91
92      // output bar chart displaying overall grade distribution
93      public void OutputBarChart()
94      {
95         Console.WriteLine( "Overall grade distribution:" );
96
97         // stores frequency of grades in each range of 10 grades
98         int[] frequency = new int[ 11 ];
99
100        // for each grade in GradeBook, increment the appropriate frequency
101        foreach ( int grade in grades )                                    
102        {                                                                  
103           ++frequency[ grade / 10 ];                                      
104        } // end foreach                                                   
105
106        // for each grade frequency, display bar in chart
107        for ( int count = 0; count < frequency.Length; count++ )
108        {
109           // output bar label ( "00-09: ", ..., "90-99: ", "100: " )
110           if ( count == 10 )
111              Console.Write( " 100: " );
112           else
113              Console.Write( "{0:D2}-{1:D2}: ",
114                 count * 10, count * 10 + 9 );
115
116           // display bar of asterisks
117           for ( int stars = 0; stars < frequency[ count ]; stars++ )
118              Console.Write( "*" );
119
120           Console.WriteLine(); // start a new line of output
121        } // end outer for
122     } // end method OutputBarChart
123
124     // output the contents of the grades array
125     public void OutputGrades()
126     {
127        Console.WriteLine( "The grades are:
" );
128        Console.Write( "            " ); // align column heads
129
130     // create a column heading for each of the tests
131     for ( int test = 0; test < grades.GetLength( 1 ); test++ )
132        Console.Write( "Test {0}  ", test + 1 );
133
134     Console.WriteLine( "Average" ); // student average column heading
135
136     // create rows/columns of text representing array grades
137     for ( int student = 0; student < grades.GetLength( 0 ); student++ )
138     {
139        Console.Write( "Student {0,2}", student + 1 );
140
141        // output student's grades
142        for ( int grade = 0; grade < grades.GetLength( 1 ); grade++ )
143           Console.Write( "{0,8}", grades[ student, grade ] );
144
145           // call method GetAverage to calculate student's average grade;
146           // pass row number as the argument to GetAverage
147           Console.WriteLine( "{0,9:F}", GetAverage( student ) );
148        } // end outer for
149     } // end method OutputGrades
150  } // end class GradeBook

Example 8.21. Create a GradeBook object using a rectangular array of grades.

 1   // Fig. 8.21: GradeBookTest.cs
 2   // Create GradeBook object using a rectangular array of grades.
 3   public class GradeBookTest
 4   {
 5      // Main method begins application execution
 6      public static void Main( string[] args )
 7      {
 8         // rectangular array of student grades    
 9         int[ , ] gradesArray = { { 87, 96, 70 },  
10                                  { 68, 87, 90 },  
11                                  { 94, 100, 90 }, 
12                                  { 100, 81, 82 }, 
13                                  { 83, 65, 85 },  
14                                  { 78, 87, 65 },  
15                                  { 85, 75, 83 },  
16                                  { 91, 94, 100 }, 
17                                  { 76, 72, 84 },  
18                                  { 87, 93, 73 } };
19
20         GradeBook myGradeBook = new GradeBook(
21            "CS101 Introduction to C# Programming", gradesArray );
22         myGradeBook.DisplayMessage();
23         myGradeBook.ProcessGrades();
24      } // end Main
25   } // end class GradeBookTest
Welcome to the grade book for
CS101 Introduction to C# Programming!

The grades are:

            Test 1  Test 2  Test 3  Average
Student  1      87      96      70    84.33
Student  2      68      87      90    81.67
Student  3      94     100      90    94.67
Student  4     100      81      82    87.67
Student  5      83      65      85    77.67
Student  6      78      87      65    76.67
Student  7      85      75      83    81.00
Student  8      91      94     100    95.00
Student  9      76      72      84    77.33
Student 10      87      93      73    84.33

Lowest grade in the grade book is 65
Highest grade in the grade book is 100
Overall grade distribution:
00-09:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: ***
70-79: ******
80-89: ***********
90-99: *******
  100: ***

Methods GetMinimum, GetMaximum and OutputBarChart each loop through array grades using the foreach statement—for example, the foreach statement from method GetMinimum (lines 50–55). To find the lowest overall grade, this foreach statement iterates through rectangular array grades and compares each element to variable lowGrade. If a grade is less than lowGrade, lowGrade is set to that grade.

When the foreach statement traverses the elements of array grades, it looks at each element of the first row in order by index, then each element of the second row in order by index and so on. The foreach statement in lines 50–55 traverses the elements of grade in the same order as the following equivalent code, expressed with nested for statements:

for ( int row = 0; row < grades.GetLength( 0 ); row++ )
   for ( int column = 0; column < grades.GetLength( 1 ); column++ )
   {
      if ( grades[ row, column ] < lowGrade )
         lowGrade = grades[ row, column ];
   }

When the foreach statement completes, lowGrade contains the lowest grade in the rectangular array. Method GetMaximum works similarly to method GetMinimum.

Method OutputBarChart (lines 93–122) displays the grade distribution as a bar chart. The syntax of the foreach statement (lines 101–104) is identical for one-dimensional and two-dimensional arrays.

Method OutputGrades (lines 125–149) uses nested for statements to output values of the array grades, in addition to each student’s semester average. The output in Fig. 8.21 shows the result, which resembles the tabular format of an instructor’s physical grade book. Lines 131–132 display the column headings for each test. We use the for statement rather than the foreach statement here so that we can identify each test with a number. Similarly, the for statement in lines 137–148 first outputs a row label using a counter variable to identify each student (line 139). Although array indices start at 0, lines 132 and 139 output test + 1 and student + 1, respectively, to produce test and student numbers starting at 1 (see Fig. 8.21). The inner for statement in lines 142–143 uses the outer for statement’s counter variable student to loop through a specific row of array grades and output each student’s test grade. Finally, line 147 obtains each student’s semester average by passing the row index of grades (i.e., student) to method GetAverage.

Method GetAverage (lines 78–90) takes one argument—the row index for a particular student. When line 147 calls GetAverage, the argument is int value student, which specifies the particular row of rectangular array grades. Method GetAverage calculates the sum of the array elements on this row, divides the total by the number of test results and returns the floating-point result as a double value (line 89).

Class GradeBookTest That Demonstrates Class GradeBook

The application in Fig. 8.21 creates an object of class GradeBook (Fig. 8.20) using the two-dimensional array of ints that gradesArray references (lines 9–18). Lines 20–21 pass a course name and gradesArray to the GradeBook constructor. Lines 22–23 then invoke myGradeBook’s DisplayMessage and ProcessGrades methods to display a welcome message and obtain a report summarizing the students’ grades for the semester, respectively.

Variable-Length Argument Lists

Variable-length argument lists allow you to create methods that receive an arbitrary number of arguments. A one-dimensional array-type argument preceded by the keyword params in a method’s parameter list indicates that the method receives a variable number of arguments with the type of the array’s elements. This use of a params modifier can occur only in the last entry of the parameter list. While you can use method overloading and array passing to accomplish much of what is accomplished with variable-length argument lists, using the params modifier is more concise.

Figure 8.22 demonstrates method Average (lines 8–17), which receives a variable-length sequence of doubles (line 8). C# treats the variable-length argument list as a one-dimensional array whose elements are all of the same type. Hence, the method body can manipulate the parameter numbers as an array of doubles. Lines 13–14 use the foreach loop to walk through the array and calculate the total of the doubles in the array. Line 16 accesses numbers.Length to obtain the size of the numbers array for use in the averaging calculation. Lines 31, 33 and 35 in Main call method Average with two, three and four arguments, respectively. Method Average has a variable-length argument list, so it can average as many double arguments as the caller passes. The output reveals that each call to method Average returns the correct value.

Example 8.22. Using variable-length argument lists.

 1   // Fig. 8.22: ParamArrayTest.cs
 2   // Using variable-length argument lists.
 3   using System;
 4
 5   public class ParamArrayTest
 6   {
 7      // calculate average
 8      public static double Average( params double[] numbers )
 9      {
10         double total = 0.0; // initialize total
11
12         // calculate total using the foreach statement
13         foreach ( double d in numbers )               
14            total += d;                                
15
16         return total / numbers.Length;
17      } // end method Average
18
19      public static void Main( string[] args )
20      {
21         double d1 = 10.0;
22         double d2 = 20.0;
23         double d3 = 30.0;
24         double d4 = 40.0;
25
26         Console.WriteLine(
27            "d1 = {0:F1}
d2 = {1:F1}
d3 = {2:F1}
d4 = {3:F1}
",
28            d1, d2, d3, d4 );
29
30         Console.WriteLine( "Average of d1 and d2 is {0:F1}",
31            Average( d1, d2 ) );
32         Console.WriteLine( "Average of d1, d2 and d3 is {0:F1}",
33            Average( d1, d2, d3 ) );
34         Console.WriteLine( "Average of d1, d2, d3 and d4 is {0:F1}",
35            Average( d1, d2, d3, d4 ) );
36      } // end Main
37   } // end class ParamArrayTest

d1 = 10.0
d2 = 20.0
d3 = 30.0
d4 = 40.0

Average of d1 and d2 is 15.0
Average of d1, d2 and d3 is 20.0
Average of d1, d2, d3 and d4 is 25.0

Common Programming Error 8.5

Common Programming Error 8.5

The params modifier may be used only with the last parameter of the parameter list.

Using Command-Line Arguments

On many systems, it’s possible to pass arguments from the command line (these are known as command-line arguments) to an application by including a parameter of type string[] (i.e., an array of strings) in the parameter list of Main, exactly as we have done in every application in the book. By convention, this parameter is named args (Fig. 8.23, line 7). When an application is executed from the Command Prompt, the execution environment passes the command-line arguments that appear after the application name to the application’s Main method as strings in the one-dimensional array args. The number of arguments passed from the command line is obtained by accessing the array’s Length property. For example, the command "MyApplication a b" passes two command-line arguments to application MyApplication. Command-line arguments are separated by white space, not commas. When the preceding command executes, the Main method entry point receives the two-element array args (i.e., args.Length is 2) in which args[0] contains the string "a" and args[1] contains the string "b". Common uses of command-line arguments include passing options and file names to applications.

Example 8.23. Using command-line arguments to initialize an array.

 1   // Fig. 8.23: InitArray.cs
 2   // Using command-line arguments to initialize an array.
 3   using System;
 4
 5   public class InitArray
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // check number of command-line arguments
10         if ( args.Length != 3 )
11            Console.WriteLine(
12               "Error: Please re-enter the entire command, including
" +
13               "an array size, initial value and increment." );
14         else
15         {
16            // get array size from first command-line argument
17            int arrayLength = Convert.ToInt32( args[ 0 ] );
18            int[] array = new int[ arrayLength ]; // create array
19
20            // get initial value and increment from command-line argument
21            int initialValue = Convert.ToInt32( args[ 1 ] );
22            int increment = Convert.ToInt32( args[ 2 ] );   
23
24            // calculate value for each array element                 
25            for ( int counter = 0; counter < array.Length; counter++ )
26               array[ counter ] = initialValue + increment * counter; 
27
28            Console.WriteLine( "{0}{1,8}", "Index", "Value" );
29
30            // display array index and value
31            for ( int counter = 0; counter < array.Length; counter++ )
32               Console.WriteLine( "{0,5}{1,8}", counter, array[ counter ] );
33         } // end else
34      } // end Main
35   } // end class InitArray
C:Examplesch08fig08_23>InitArray.exe
Error: Please re-enter the entire command, including
an array size, initial value and increment.
C:Examplesch08fig08_23>InitArray.exe 5 0 4
Index   Value
    0       0
    1       4
    2       8
    3      12
    4      16
C:Examplesch08fig08_23>InitArray.exe 10 1 2
Index   Value
    0       1
    1       3
    2       5
    3       7
    4       9
    5      11
    6      13
    7      15
    8      17
    9      19

Figure 8.23 uses three command-line arguments to initialize an array. When the application executes, if args.Length is not 3, the application displays an error message and terminates (lines 10–13). Otherwise, lines 16–32 initialize and display the array based on the values of the command-line arguments.

The command-line arguments become available to Main as strings in args. Line 17 gets args[0]—a string that specifies the array size—and converts it to an int value, which the application uses to create the array in line 18. The static method ToInt32 of class Convert converts its string argument to an int.

Lines 21–22 convert the args[1] and args[2] command-line arguments to int values and store them in initialValue and increment, respectively. Lines 25–26 calculate the value for each array element.

The first sample execution indicates that the application received an insufficient number of command-line arguments. The second sample execution uses command-line arguments 5, 0 and 4 to specify the size of the array (5), the value of the first element (0) and the increment of each value in the array (4), respectively. The corresponding output indicates that these values create an array containing the integers 0, 4, 8, 12 and 16. The output from the third sample execution illustrates that the command-line arguments 10, 1 and 2 produce an array whose 10 elements are the nonnegative odd integers from 1 to 19.

Wrap-Up

This chapter began our introduction to data structures, exploring the use of arrays to store data in and retrieve data from lists and tables of values. The chapter examples demonstrated how to declare array variables, initialize arrays and refer to individual elements of arrays. The chapter introduced the foreach statement as an additional means (besides the for statement) for iterating through arrays. We showed how to pass arrays to methods and how to declare and manipulate multidimensional arrays. Finally, the chapter showed how to write methods that use variable-length argument lists and how to read arguments passed to an application from the command line.

We continue our coverage of data structures in Chapter 9, where we discuss the List collection, which is a dynamically resizable array-based collection. Chapter 20 discusses searching and sorting algorithms. Chapter 21 introduces dynamic data structures, such as lists, queues, stacks and trees, that can grow and shrink as applications execute. Chapter 22 presents generics, which provide the means to create general models of methods and classes that can be declared once, but used with many different data types. Chapter 23 introduces the data structure classes provided by the .NET Framework, some of which use generics to allow you to specify the exact types of objects that a particular data structure will store. You can use these predefined data structures instead of building your own. Chapter 23 discusses many data-structure classes that can grow and shrink in response to an application’s changing storage requirements. The .NET Framework also provides class Array, which contains utility methods for array manipulation. Chapter 23 uses several static methods of class Array to perform such manipulations as sorting and searching the data in an array.

We’ve now introduced the basic concepts of classes, objects, control statements, methods and arrays. In Chapter 9 we introduce Language Integrated Query (LINQ), which enables you to write expressions that can retrieve information from a wide variety of data sources, such as arrays. You’ll see how to search, sort and filter data using LINQ.

If you’re in a course which either skips LINQ or defers coverage until later in the book when it’s needed to support other more advanced C# features, you can proceed to Chapter 10 in which we take a deeper look at classes and objects.

Summary

Section 8.1 Introduction

  • Arrays are data structures consisting of related data items of the same type. Arrays are fixed-length entities—they remain the same length once they’re created.

Section 8.2 Arrays

  • Arrays are reference types. What we typically think of as an array is actually a reference to an array object. The elements of an array can be either value types or reference types (including other arrays).

  • To refer to a particular element in an array, we specify the name of the reference to the array and the index (i.e., the position number) of the element in the array.

  • An application refers to an array element with an array-access expression that includes the name of the array, followed by the index of the particular element in square brackets ([]).

  • The first element in every array has index zero and is sometimes called the zeroth element.

  • An array’s Length property returns the number of elements in the array.

Section 8.3 Declaring and Creating Arrays

  • To create an array instance, you specify the type and the number of array elements as part of an array-creation expression that uses keyword new. The following declaration and array-creation expression create an array object containing 12 int elements:

    int[] a = new int[ 12 ];
  • When an array is created, each element of the array receives a default value—0 for the numeric simple-type elements, false for bool elements and null for references.

  • An application can declare variables that reference arrays of any type. Every element of a valuetype array contains a value of the array’s declared type. In an array of a reference type, every element is a reference to an object of the array’s declared type or null.

Section 8.4 Examples Using Arrays

  • An application can create an array and initialize its elements with an array initializer, which is a comma-separated list of expressions (called an initializer list) enclosed in braces.

  • Constants must be initialized when they’re declared and cannot be modified thereafter.

  • In a format item, a D format specifier indicates that the value should be formatted as an integer, and the number after the D indicates how many digits this formatted integer must contain.

  • When a program is executed, array element indices are checked for validity—all indices must be greater than or equal to 0 and less than the length of the array. If an attempt is made to use an invalid index to access an element, an IndexOutOfRangeException exception occurs.

  • An exception indicates a problem that occurs while a program executes. The name “exception” suggests that the problem occurs infrequently—if the “rule” is that a statement normally executes correctly, then the problem represents the “exception to the rule.”

  • Exception handling enables you to create fault-tolerant programs that can resolve exceptions.

  • To handle an exception, place any code that might throw an exception in a try statement.

  • The try block contains the code that might throw an exception, and the catch block contains the code that handles the exception if one occurs.

  • You can have many catch blocks to handle different types of exceptions that might be thrown in the corresponding try block.

  • When a try block terminates any variables declared in the try block go out of scope.

  • A catch block declares a type and an exception parameter. Inside the catch block, you can use the parameter’s identifier to interact with a caught exception object.

  • An exception object’s Message property returns the exception’s error message.

Section 8.5 Case Study: Card Shuffling and Dealing Simulation

  • The ToString method of an object is called implicitly in many cases when the object is used where a string is expected.

Section 8.6 foreach Statement

  • The foreach statement iterates through the elements of an entire array or collection. The syntax of a foreach statement is:

    foreach ( type identifier in arrayName )
       statement

    where type and identifier are the type and name of the iteration variable, and arrayName is the array through which to iterate.

  • The foreach header can be read concisely as “for each iteration, assign the next element of the array to the iteration variable, then execute the following statement.”

  • The foreach statement can be used only to access array elements, but it cannot be used to modify elements. Any attempt to change the value of the iteration variable in the body of a foreach statement will cause a compilation error.

Section 8.7 Passing Arrays and Array Elements to Methods

  • When an argument to a method is an entire array or an individual array element of a reference type, the called method receives a copy of the reference. However, when an argument to a method is an individual array element of a value type, the called method receives a copy of the element’s value.

Section 8.8 Passing Arrays by Value and by Reference

  • When a reference-type object is passed with ref, the called method actually gains control over the reference itself, allowing the called method to replace the original reference in the caller with a different object or even with null.

  • If you encounter a situation where you truly want the called procedure to modify the caller’s reference, pass the reference-type parameter using keyword ref—but such situations are rare.

Section 8.10 Multidimensional Arrays

  • Two-dimensional arrays are often used to represent tables of values consisting of information arranged in rows and columns. To identify a particular table element, we must specify two indices.

  • C# supports two types of two-dimensional arrays—rectangular arrays and jagged arrays.

  • Rectangular arrays are used to represent tables of information in the form of rows and columns, where each row has the same number of columns.

  • Elements in rectangular array a are identified by an expression of the form a[row, column].

  • A rectangular array could be declared and initialized with array initializers of the form:

    arrayType[ , ] arrayName = { {row0 initializer}, {row1 initializer}, ... };

    provided that each row of the rectangular array must have the same length.

  • A rectangular array can be created with an array-creation expression of the form

    arrayType[ , ] arrayName = new arrayType[ numRows, numColumns ];
  • A jagged array is maintained as a one-dimensional array in which each element refers to a one-dimensional array.

  • The lengths of the rows in a jagged array need not be the same.

  • We can access the elements in a jagged array arrayName by an array-access expression of the form arrayName[ row ][ column ].

  • A jagged array can be declared and initialized in the form:

    arrayType[][] arrayName = { new arrayType[] {row0 initializer},
                             new arrayType[] {row1 initializer}, ... };

Section 8.11 Case Study: Class GradeBook Using a Rectangular Array

  • When the foreach statement traverses a rectangular array’s elements, it looks at each element of the first row in order by index, then each element of the second row in order by index and so on.

Section 8.12 Variable-Length Argument Lists

  • A one-dimensional array parameter preceded by params in a method’s parameter list indicates that the method receives a variable number of arguments with the type of the array’s elements.

  • The params modifier can appear only in the last entry of the parameter list.

  • C# treats a variable-length argument list as a one-dimensional array.

Section 8.13 Using Command-Line Arguments

  • When an application is executed from the Command Prompt, the execution environment passes the command-line arguments that appear after the application name to the application’s Main method as strings in a one-dimensional array.

Self-Review Exercises

8.1

Fill in the blank(s) in each of the following statements:

  1. Lists and tables of values can be stored in __________.

  2. An array is a group of __________ (called elements) containing values that all have the same __________.

  3. The __________statement allows you to iterate through the elements in an array without using a counter.

  4. The number that refers to a particular array element is called the element’s __________.

  5. An array that uses two indices is referred to as a(n) __________ array.

  6. Use the foreach header __________ to iterate through double array numbers.

  7. Command-line arguments are stored in __________.

  8. Use the expression __________ to receive the total number of arguments in a command line. Assume that command-line arguments are stored in string[] args.

  9. Given the command MyApplication test, the first command-line argument is __________.

  10. A(n) __________ in the parameter list of a method indicates that the method can receive a variable number of arguments.

8.1

  1. arrays.

  2. variables, type.

  3. foreach.

  4. index (or position number).

  5. two-dimensional.

  6. foreach ( double d in numbers ).

  7. an array of strings, usually called args.

  8. args.Length.

  9. test.

  10. params modifier.

8.2

Determine whether each of the following is true or false. If false, explain why.

  1. A single array can store values of many different types.

  2. An array index should normally be of type float.

  3. An individual array element that’s passed to a method and modified in that method will contain the modified value when the called method completes execution.

  4. Command-line arguments are separated by commas.

8.2

  1. False. An array can store only values of the same type.

  2. False. An array index must be an integer or an integer expression.

  3. For individual value-type elements of an array: False. A called method receives and manipulates a copy of the value of such an element, so modifications do not affect the original value. If the reference of an array is passed to a method, however, modifications to the array elements made in the called method are indeed reflected in the original. For individual elements of a reference type: True. A called method receives a copy of the reference of such an element, and changes to the referenced object will be reflected in the original array element.

  4. False. Command-line arguments are separated by whitespace.

8.3

Perform the following tasks for an array called fractions:

  1. Declare constant ARRAY_SIZE initialized to 10.

  2. Declare variable fractions which will reference an array with ARRAY_SIZE elements of type double. Initialize the elements to 0.

  3. Name the element of the array with index 3.

  4. Assign the value 1.667 to the array element with index 9.

  5. Assign the value 3.333 to the array element with index 6.

  6. Sum all the elements of the array, using a for statement. Declare integer variable x as a control variable for the loop.

8.3

  1. const int ARRAY_SIZE = 10;
  2. double[] fractions = new double[ ARRAY_SIZE ];
  3. fractions[ 3 ]
  4. fractions[ 9 ] = 1.667;
  5. fractions[ 6 ] = 3.333;
  6. double total = 0.0;
    for ( int x = 0; x < fractions.Length; x++ )
       total += fractions[ x ];

8.4

Perform the following tasks for an array called table:

  1. Declare the variable and initialize it with a rectangular integer array that has three rows and three columns. Assume that constant ARRAY_SIZE has been declared to be 3.

  2. How many elements does the array contain?

  3. Use a for statement to initialize each element of the array to the sum of its indices.

8.4

  1. int[ , ] table = new int[ ARRAY_SIZE, ARRAY_SIZE ];
  2. Nine.

  3. for ( int x = 0; x < table.GetLength( 0 ); x++ )
       for ( int y = 0; y < table.GetLength( 1 ); y++ )
          table[ x, y ] = x + y;

8.5

Find and correct the error in each of the following code segments:

  1. const int ARRAY_SIZE = 5;
    ARRAY_SIZE = 10;
  2. Assume int[] b = new int[ 10 ];
    for ( int i = 0; i <= b.Length; i++ )
       b[ i ] = 1;
  3. Assume int[ , ] a = { { 1, 2 }, { 3, 4 } };
       a[ 1 ][ 1 ] = 5;

8.5

  1. Error: Assigning a value to a constant after it’s been initialized.

    Correction: Assign the correct value to the constant in the const declaration.

  2. Error: Referencing an array element outside the bounds of the array (b[10]).

    Correction: Change the <= operator to <.

  3. Error: Array indexing is performed incorrectly.

    Correction: Change the statement to a[ 1, 1 ] = 5;.

Answers to Self-Review Exercises

Exercises

8.6

Fill in the blanks in each of the following statements:

  1. One-dimensional array p contains four elements. The names of those elements are __________, __________, __________ and __________.

  2. Naming an array’s variable, stating its type and specifying the number of dimensions in the array is called __________ the array.

  3. In a two-dimensional array, the first index identifies the __________ of an element and the second index identifies the __________ of an element.

  4. An m-by-n array contains __________rows, __________columns and __________elements.

  5. The name of the element in row 3 and column 5 of jagged array d is __________.

8.7

Determine whether each of the following is true or false. If false, explain why.

  1. To refer to a particular location or element within an array, we specify the name of the array’s variable and the value of the particular element.

  2. The declaration of a variable that references an array reserves memory for the array.

  3. To indicate that 100 locations should be reserved for integer array p, the programmer writes the declaration p[ 100 ];

  4. An application that initializes the elements of a 15-element array to 0 must contain at least one for statement.

  5. To total the elements of a two-dimensional array you must use nested for statements.

8.8

Write C# statements to accomplish each of the following tasks:

  1. Display the value of the element of character array f with index 6.

  2. Initialize each of the five elements of one-dimensional integer array g to 8.

  3. Total the 100 elements of floating-point array c.

  4. Copy 11-element array a into the first portion of array b, which contains 34 elements.

  5. Determine and display the smallest and largest values contained in 99-element floatingpoint array w.

8.9

Consider the tw‘o-by-three rectangular integer array t.

  1. Write a statement that declares t and creates the array.

  2. How many rows does t have?

  3. How many columns does t have?

  4. How many elements does t have?

  5. Write the names of all the elements in row 1 of t.

  6. Write the names of all the elements in column 2 of t.

  7. Write a single statement that sets the element of t in row 0 and column 1 to zero.

  8. Write a sequence of statements that initializes each element of t to 1. Do not use a repetition statement.

  9. Write a nested for statement that initializes each element of t to 3.

  10. Write a nested for statement that inputs values for the elements of t from the user.

  11. Write a sequence of statements that determines and displays the smallest value in t.

  12. Write a statement that displays the elements of row 0 of t.

  13. Write a statement that totals the elements of column 2 of t.

  14. Write a sequence of statements that displays the contents of t in tabular format. List the column indices as headings across the top, and list the row indices at the left of each row.

8.10

(Sales Commissions) Use a one-dimensional array to solve the following problem: A company pays its salespeople on a commission basis. The salespeople receive $200 per week plus 9% of their gross sales for that week. For example, a salesperson who grosses $5000 in sales in a week receives $200 plus 9% of $5000, or a total of $650. Write an application (using an array of counters) that determines how many of the salespeople earned salaries in each of the following ranges (assume that each salesperson’s salary is an integer). Summarize the results in tabular format.

  1. $200–299

  2. $300–399

  3. $400–499

  4. $500–599

  5. $600–699

  6. $700–799

  7. $800–899

  8. $900–999

  9. $1000 and over

8.11

(Array Manipulations) Write statements that perform the following one-dimensional-array operations:

  1. Set the three elements of integer array counts to 0.

  2. Add 1 to each of the four elements of integer array bonus.

  3. Display the five values of integer array bestScores in column format.

8.12

(Duplicate Elimination) Use a one-dimensional array to solve the following problem: Write an application that inputs five numbers, each of which is between 10 and 100, inclusive. As each number is read, display it only if it’s not a duplicate of a number already read. Provide for the “worst case,” in which all five numbers are different. Use the smallest possible array to solve this problem. Display the complete set of unique values input after the user inputs each new value.

8.13

(Jagged Arrays) List the elements of the three-by-five jagged array sales in the order in which they’re set to 0 by the following code segment:

for ( int row = 0; row < sales.Length; row++ )
{
   for ( int col = 0; col < sales[row].Length; col++ )
   {
      sales[ row ][ col ] = 0;
   }
}

8.14

(Variable-Length Argument List) Write an application that calculates the product of a series of integers that are passed to method product using a variable-length argument list. Test your method with several calls, each with a different number of arguments.

8.15

(Command-Line Arguments) Rewrite Fig. 8.2 so that the array’s size is specified by the first command-line argument. If no command-line argument is supplied, use 10 as the default size.

8.16

(Using the foreach Statement) Write an application that uses a foreach statement to sum the double values passed by the command-line arguments. [Hint: Use static method ToDouble of class Convert to convert a string to a double value.]

8.17

(Dice Rolling) Write an application to simulate the rolling of two dice. The application should use an object of class Random once to roll the first die and again to roll the second die. The sum of the two values should then be calculated. Each die can show an integer value from 1 to 6, so the sum of the values will vary from 2 to 12, with 7 being the most frequent sum and 2 and 12 the least frequent sums. Figure 8.24 shows the 36 possible combinations of the two dice. Your application should roll the dice 36,000 times. Use a one-dimensional array to tally the number of times each possible sum appears. Display the results in tabular format. Determine whether the totals are reasonable (e.g., there are six ways to roll a 7, so approximately one-sixth of the rolls should be 7).

The 36 possible sums of two dice.

Figure 8.24. The 36 possible sums of two dice.

8.18

(Game of Craps) Write an application that runs 1000 games of craps (Fig. 7.8) and answers the following questions:

  1. How many games are won on the first roll, second roll, ..., twentieth roll and after the twentieth roll?

  2. How many games are lost on the first roll, second roll, ..., twentieth roll and after the twentieth roll?

  3. What are the chances of winning at craps? [Note: You should discover that craps is one of the fairest casino games.]

  4. What is the average length of a game of craps?

8.19

(Airline Reservations System) A small airline has just purchased a computer for its new automated reservations system. You have been asked to develop the new system. You’re to write an application to assign seats on each flight of the airline’s only plane (capacity: 10 seats).

Display the following alternatives: Please type 1 for First Class and Please type 2 for Economy. If the user types 1, your application should assign a seat in the first-class section (seats 1–5). If the user types 2, your application should assign a seat in the economy section (seats 6–10).

Use a one-dimensional array of type bool to represent the seating chart of the plane. Initialize all the elements of the array to false to indicate that all the seats are empty. As each seat is assigned, set the corresponding element of the array to true to indicate that the seat is no longer available.

Your application should never assign a seat that has already been assigned. When the economy section is full, your application should ask the person if it’s acceptable to be placed in the first-class section (and vice versa). If yes, make the appropriate seat assignment. If no, display the message "Next flight leaves in 3 hours."

8.20

(Total Sales) Use a rectangular array to solve the following problem: A company has three salespeople (1 to 3) who sell five different products (1 to 5). Once a day, each salesperson passes in a slip for each type of product sold. Each slip contains the following:

  1. The salesperson number

  2. The product number

  3. The total dollar value of that product sold that day

Thus, each salesperson passes in between 0 and 5 sales slips per day. Assume that the information from all of the slips for last month is available. Write an application that will read all the information for last month’s sales and summarize the total sales by salesperson and by product. All totals should be stored in rectangular array sales. After processing all the information for last month, display the results in tabular format, with each column representing a particular salesperson and each row representing a particular product. Cross-total each row to get the total sales of each product for last month. Cross-total each column to get the total sales by salesperson for last month. Your tabular output should include these cross-totals to the right of the totaled rows and below the totaled columns.

8.21

(Turtle Graphics) The Logo language made the concept of turtle graphics famous. Imagine a mechanical turtle that walks around the room under the control of a C# application. The turtle holds a pen in one of two positions—up or down. While the pen is down, the turtle traces out shapes as it moves, and while the pen is up, the turtle moves about freely without writing anything. In this problem, you’ll simulate the operation of the turtle and create a computerized sketchpad.

Use a 20-by-20 rectangular array floor that’s initialized to 0. Read commands from an array that contains them. Keep track at all times of the current position of the turtle and whether the pen is currently up or down. Assume that the turtle always starts at position (0, 0) of the floor with its pen up. The set of turtle commands your application must process are shown in Fig. 8.25.

Table 8.25. Turtle graphics commands.

Command

Meaning

1

Pen up

2

Pen down

3

Turn right

4

Turn left

5,10

Move forward 10 spaces (replace 10 for a different number of spaces)

6

Display the 20-by-20 array

9

End of data (sentinel)

Suppose that the turtle is somewhere near the center of the floor. The following “application” would draw and display a 12-by-12 square, leaving the pen in the up position:

2
5,12
3
5,12
3
5,12
3
5,12
1
6
9

As the turtle moves with the pen down, set the appropriate elements of array floor to 1s. When the 6 command (display the array) is given, wherever there’s a 1 in the array, display an asterisk or any character you choose. Wherever there’s a 0, display a blank.

Write an application to implement the turtle graphics capabilities discussed here. Write several turtle graphics applications to draw interesting shapes. Add other commands to increase the power of your turtle graphics language.

8.22

(Knight’s Tour) One of the more interesting puzzlers for chess buffs is the Knight’s Tour problem, originally proposed by the mathematician Euler. Can the chess piece called the knight move around an empty chessboard and touch each of the 64 squares once and only once? We study this intriguing problem in depth here.

The knight makes only L-shaped moves (two spaces in one direction and one space in a perpendicular direction). Thus, as shown in Fig. 8.26, from a square near the middle of an empty chessboard, the knight (labeled K) can make eight different moves (numbered 0 through 7).

The eight possible moves of the knight.

Figure 8.26. The eight possible moves of the knight.

  1. Draw an eight-by-eight chessboard on a sheet of paper, and attempt a Knight’s Tour by hand. Put a 1 in the starting square, a 2 in the second square, a 3 in the third and so on. Before starting the tour, estimate how far you think you’ll get, remembering that a full tour consists of 64 moves. How far did you get? Was this close to your estimate?

  2. Now let’s develop an application that will move the knight around a chessboard. The board is represented by an eight-by-eight rectangular array board. Each square is initialized to zero. We describe each of the eight possible moves in terms of their horizontal and vertical components. For example, a move of type 0, as shown in Fig. 8.26, consists of moving two squares horizontally to the right and one square vertically upward. A move of type 2 consists of moving one square horizontally to the left and two squares vertically upward. Horizontal moves to the left and vertical moves upward are indicated with negative numbers. The eight moves may be described by two one-dimensional arrays, horizontal and vertical, as follows:

    horizontal[ 0 ] = 2        vertical[ 0 ] = -1 
    horizontal[ 1 ] = 1        vertical[ 1 ] = -2
    horizontal[ 2 ] = -1       vertical[ 2 ] = -2
    horizontal[ 3 ] = -2       vertical[ 3 ] = -1
    horizontal[ 4 ] = -2       vertical[ 4 ] = 1
    horizontal[ 5 ] = -1       vertical[ 5 ] = 2
    horizontal[ 6 ] = 1        vertical[ 6 ] = 2
    horizontal[ 7 ] = 2        vertical[ 7 ] = 1

    Let variables currentRow and currentColumn indicate the row and column, respectively, of the knight’s current position. To make a move of type moveNumber, where moveNumber is between 0 and 7, your application should use the statements

    currentRow += vertical[ moveNumber ];
    currentColumn += horizontal[ moveNumber ];

    Write an application to move the knight around the chessboard. Keep a counter that varies from 1 to 64. Record the latest count in each square the knight moves to. Test each potential move to see if the knight has already visited that square. Test every potential move to ensure that the knight does not land off the chessboard. Run the application. How many moves did the knight make?

  3. After attempting to write and run a Knight’s Tour application, you have probably developed some valuable insights. We’ll use these insights to develop a heuristic for moving the knight. Heuristics do not guarantee success, but a carefully developed heuristic greatly improves the chance of success. You may have observed that the outer squares are more troublesome than the squares nearer the center of the board. In fact, the most troublesome and inaccessible squares are the four corners.

    Intuition may suggest that you should attempt to move the knight to the most troublesome squares first and leave open those that are easiest to get to, so that when the board gets congested near the end of the tour, there will be a greater chance of success.

    We could develop an “accessibility heuristic” by classifying each of the squares according to how accessible it is and always moving the knight (using the knight’s L-shaped moves) to the most inaccessible square. We label two-dimensional array accessibility with numbers indicating from how many squares each particular square is accessible. On a blank chessboard, each of the 16 squares nearest the center is rated as 8, each corner square is rated as 2, and the other squares have accessibility numbers of 3, 4 or 6 as follows:

    2  3  4  4  4  4  3  2
    3  4  6  6  6  6  4  3
    4  6  8  8  8  8  6  4
    4  6  8  8  8  8  6  4
    4  6  8  8  8  8  6  4
    4  6  8  8  8  8  6  4
    3  4  6  6  6  6  4  3
    2  3  4  4  4  4  3  2

    Write a new version of the Knight’s Tour, using the accessibility heuristic. The knight should always move to the square with the lowest accessibility number. In case of a tie, the knight may move to any of the tied squares. Therefore, the tour may begin in any of the four corners. [Note: As the knight moves around the chessboard as more squares become occupied, your application should reduce the accessibility numbers. In this way, at any given time during the tour, each available square’s accessibility number will remain equal to precisely the number of squares from which that square may be reached.] Run this version of your application. Did you get a full tour? Modify the application to run 64 tours, one starting from each square of the chessboard. How many full tours did you get?

  4. Write a version of the Knight’s Tour application that, when encountering a tie between two or more squares, decides what square to choose by looking ahead to those squares reachable from the “tied” squares. Your application should move to the tied square for which the next move would arrive at the square with the lowest accessibility number.

8.23

(Knight’s Tour: Brute-Force Approaches) In Part c of Exercise 8.22, we developed a solution to the Knight’s Tour problem. The approach used, called the “accessibility heuristic,” generates many solutions and executes efficiently.

As computers continue to increase in power, we’ll be able to solve more problems with sheer computer power and relatively unsophisticated algorithms. Let’s call this approach “brute-force” problem solving.

  1. Use random-number generation to enable the knight to walk around the chessboard (in its legitimate L-shaped moves) at random. Your application should run one tour and display the final chessboard. How far did the knight get?

  2. Most likely, the application in Part a produced a relatively short tour. Now modify your application to attempt 1000 tours. Use a one-dimensional array to keep track of the number of tours of each length. When your application finishes attempting the 1000 tours, it should display this information in neat tabular format. What was the best result?

  3. Most likely, the application in Part b gave you some “respectable” tours, but no full tours. Now let your application run until it produces a full tour. Once again, keep a table of the number of tours of each length, and display this table when the first full tour is found. How many tours did your application attempt before producing a full tour?

  4. Compare the brute-force version of the Knight’s Tour with the accessibility-heuristic version. Which required a more careful study of the problem? Which algorithm was more difficult to develop? Which required more computer power? Could we be certain (in advance) of obtaining a full tour with the accessibility-heuristic approach? Could we be certain (in advance) of obtaining a full tour with the brute-force approach? Argue the pros and cons of brute-force problem solving in general.

8.24

(Eight Queens) Another puzzler for chess buffs is the Eight Queens problem, which asks: Is it possible to place eight queens on an empty chessboard so that no queen is “attacking” any other (i.e., no two queens are in the same row, in the same column or along the same diagonal)? Use the thinking developed in Exercise 8.22 to formulate a heuristic for solving the Eight Queens problem. Run your application. [Hint: It’s possible to assign a value to each square of the chessboard to indicate how many squares of an empty chessboard are “eliminated” if a queen is placed in that square. Each of the corners would be assigned the value 22, as demonstrated by Fig. 8.27. Once these “elimination numbers” are placed in all 64 squares, an appropriate heuristic might be as follows: Place the next queen in the square with the smallest elimination number. Why is this strategy intuitively appealing?]

The 22 squares eliminated by placing a queen in the upper left corner.

Figure 8.27. The 22 squares eliminated by placing a queen in the upper left corner.

8.25

(Eight Queens: Brute-Force Approaches) In this exercise, you’ll develop several brute-force approaches to solving the Eight Queens problem introduced in Exercise 8.24.

  1. Use the random brute-force technique developed in Exercise 8.23 to solve the Eight Queens problem.

  2. Use an exhaustive technique (i.e., try all possible combinations of eight queens on the chessboard) to solve the Eight Queens problem.

8.26

(Knight’s Tour: Closed-Tour Test) In the Knight’s Tour (Exercise 8.22), a full tour occurs when the knight makes 64 moves, touching each square of the chessboard once and only once. A closed tour occurs when the 64th move is one move away from the square in which the tour started. Modify the application you wrote in Exercise 8.22 to test for a closed tour if a full tour has occurred.

8.27

(Sieve of Eratosthenes) A prime number is any integer greater than 1 that’s evenly divisible only by itself and 1. The Sieve of Eratosthenes finds prime numbers. It operates as follows:

  1. Create a simple type bool array with all elements initialized to true. Array elements with prime indices will remain true. All other array elements will eventually be set to false.

  2. Starting with array index 2, determine whether a given element is true. If so, loop through the remainder of the array and set to false every element whose index is a multiple of the index for the element with value true. Then continue the process with the next element with value true. For array index 2, all elements beyond element 2 in the array with indices that are multiples of 2 (indices 4, 6, 8, 10, etc.) will be set to false; for array index 3, all elements beyond element 3 in the array with indices that are multiples of 3 (indices 6, 9, 12, 15, etc.) will be set to false; and so on.

When this process completes, the array elements that are still true indicate that the index is a prime number. These indices can be displayed. Write an application that uses an array of 1000 elements to determine and display the prime numbers between 2 and 999. Ignore elements 0 and 1.

8.28

(Simulation: The Tortoise and the Hare) You’ll now re-create the classic race of the tortoise and the hare. You’ll use random-number generation to develop a simulation of this memorable event.

Our contenders begin the race at square 1 of 70 squares. Each square represents a possible position along the race course. The finish line is at square 70. The first contender to reach or pass square 70 is rewarded with a pail of fresh carrots and lettuce. The course weaves its way up the side of a slippery mountain, so occasionally the contenders lose ground.

A clock ticks once per second. With each tick of the clock, your application should adjust the position of the animals according to the rules in Fig. 8.28. Use variables to keep track of the positions of the animals (i.e., position numbers are 1–70). Start each animal at position 1 (the “starting gate”). If an animal slips left before square 1, move it back to square 1.

Table 8.28. Rules for adjusting the positions of the tortoise and the hare.

Animal

Move type

Percentage of the time

Actual move

Tortoise

Fast plod

50%

3 squares to the right

 

Slip

20%

6 squares to the left

 

Slow plod

30%

1 square to the right

Hare

Sleep

20%

No move at all

 

Big hop

20%

9 squares to the right

 

Big slip

10%

12 squares to the left

 

Small hop

30%

1 square to the right

 

Small slip

20%

2 squares to the left

Generate the percentages in Fig. 8.28 by producing a random integer i in the range 1 ≤ i ≤ 10. For the tortoise, perform a “fast plod” when 1 ≤ i ≤ 5, a “slip” when 6 ≤ i ≤ 7 or a “slow plod” when 8 ≤ i ≤ 10. Use a similar technique to move the hare.

Begin the race by displaying

ON YOUR MARK, GET SET
BANG !!!!!
AND THEY'RE OFF !!!!!

Then, for each tick of the clock (i.e., each repetition of a loop), display a 70-position line showing the letter T in the position of the tortoise and the letter H in the position of the hare. Occasionally, the contenders will land on the same square. In this case, the tortoise bites the hare, and your application should display OUCH!!! beginning at that position. All output positions other than the T, the H or the OUCH!!! (in case of a tie) should be blank.

After each line is displayed, test for whether either animal has reached or passed square 70. If so, display the winner and terminate the simulation. If the tortoise wins, display TORTOISE WINS!!! YAY!!! If the hare wins, display Hare wins. Yuch. If both animals win on the same tick of the clock, you may want to favor the tortoise (the “underdog”), or you may want to display It's a tie. If neither animal wins, perform the loop again to simulate the next tick of the clock. When you’re ready to run your application, assemble a group of fans to watch the race. You’ll be amazed at how involved your audience gets!

8.29

(Card Shuffling and Dealing) Modify the application of Fig. 8.11 to deal a five-card poker hand. Then modify class DeckOfCards of Fig. 8.10 to include methods that determine whether a hand contains

  1. a pair

  2. two pairs

  3. three of a kind (e.g., three jacks)

  4. four of a kind (e.g., four aces)

  5. a flush (i.e., all five cards of the same suit)

  6. a straight (i.e., five cards of consecutive face values)

  7. a full house (i.e., two cards of one face value and three cards of another face value)

[Hint: Add methods GetFace and GetSuit to class Card of Fig. 8.9.]

8.30

(Card Shuffling and Dealing) Use the methods developed in Exercise 8.29 to write an application that deals two five-card poker hands, evaluates each hand and determines which is better.

Special Section: Building Your Own Computer

In the next several problems, we take a temporary diversion from the world of high-level language programming to “peel open” a computer and look at its internal structure. We introduce machine-language programming and write several machine-language programs. To make this an especially valuable experience, we then build a computer (through the technique of software-based simulation) on which you can execute your machine-language programs.

8.31

(Machine-Language Programming) Let’s create a computer called the Simpletron. As its name implies, it’s a simple machine, but powerful. The Simpletron runs programs written in the only language it directly understands: Simpletron Machine Language, or SML for short.

The Simpletron contains an accumulator—a special register into which information is put before the Simpletron uses it in calculations or examines it in various ways. All the information in the Simpletron is handled in terms of words. A word is a signed four-digit decimal number, such as +3364, -1293, +0007 and -0001. The Simpletron is equipped with a 100-word memory, and these words are referenced by their location numbers 00, 01, ..., 99.

Before running an SML program, we must load, or place, the code into memory. The first instruction (or statement) of every SML program is always placed in location 00. The simulator will start executing at this location.

Each instruction written in SML occupies one word of the Simpletron’s memory (hence, instructions are signed four-digit decimal numbers). We shall assume that the sign of an SML instruction is always plus, but the sign of a data word may be either plus or minus. Each location in the Simpletron’s memory may contain an instruction, a data value used by a program or an unused (and hence undefined) area of memory. The first two digits of each SML instruction are the operation code specifying the operation to be performed. SML operation codes are summarized in Fig. 8.29.

Table 8.29. Simpletron Machine Language (SML) operation codes.

Operation code

Meaning

Input/output operations:

 
const int READ = 10;

Read a word from the keyboard into a specific location in memory.

const int WRITE = 11;

Write a word from a specific location in memory to the screen.

Load/store operations:

 
const int LOAD = 20;

Load a word from a specific location in memory into the accumulator.

const int STORE = 21;

Store a word from the accumulator into a specific location in memory.

Arithmetic operations:

 
const int ADD = 30;

Add a word from a specific location in memory to the word in the accumulator (leave the result in the accumulator).

const int SUBTRACT = 31;

Subtract a word from a specific location in memory from the word in the accumulator (leave the result in the accumulator).

const int DIVIDE = 32;

Divide a word from a specific location in memory into the word in the accumulator (leave result in the accumulator).

const int MULTIPLY = 33;

Multiply a word from a specific location in memory by the word in the accumulator (leave the result in the accumulator).

Transfer of control operations:

 
const int BRANCH = 40;

Branch to a specific location in memory.

const int BRANCHNEG = 41;

Branch to a specific location in memory if the accumulator is negative.

const int BRANCHZERO = 42;

Branch to a specific location in memory if the accumulator is zero.

const int HALT = 43;

Halt. The program has completed its task.

The last two digits of an SML instruction are the operand—the address of the memory location containing the word to which the operation applies. Let’s consider several simple SML programs. The first SML program (Fig. 8.30) reads two numbers from the keyboard, then computes and displays their sum. The instruction +1007 reads the first number from the keyboard and places it into location 07 (which has been initialized to 0). Then instruction +1008 reads the next number into location 08. The load instruction, +2007, puts the first number into the accumulator, and the add instruction, +3008, adds the second number to the number in the accumulator. All SML arithmetic instructions leave their results in the accumulator. The store instruction, +2109, places the result in memory location 09, from which the write instruction, +1109, takes the number and displays it (as a signed four-digit decimal number). The halt instruction, +4300, terminates execution.

Table 8.30. SML program that reads two integers and computes their sum.

Location

Number

Instruction

00

+1007

(Read A)

01

+1008

(Read B)

02

+2007

(Load A)

03

+3008

(Add B)

04

+2109

(Store C)

05

+1109

(Write C)

06

+4300

(Halt)

07

+0000

(Variable A)

08

+0000

(Variable B)

09

+0000

(Result C)

The second SML program (Fig. 8.31) reads two numbers from the keyboard and determines and displays the larger value. Note the use of the instruction +4107 as a conditional transfer of control, much the same as C#’s if statement.

Table 8.31. SML program that reads two integers and determines the larger.

Location

Number

Instruction

00

+1009

(Read A)

01

+1010

(Read B)

02

+2009

(Load A)

03

+3110

(Subtract B)

04

+4107

(Branch negative to 07)

05

+1109

(Write A)

06

+4300

(Halt)

07

+1110

(Write B)

08

+4300

(Halt)

09

+0000

(Variable A)

10

+0000

(Variable B)

Now write SML programs to accomplish each of the following tasks:

  1. Use a sentinel-controlled loop to read positive numbers and compute and display their sum. Terminate input when a negative number is entered.

  2. Use a counter-controlled loop to read seven numbers, some positive and some negative, then compute and display their average.

  3. Read a series of numbers, then determine and display the largest number. The first number read indicates how many numbers should be processed.

8.32

(Computer Simulator) In this problem, you’re going to build your own computer. No, you’ll not be soldering components together. Rather, you’ll use the powerful technique of software-based simulation to create an object-oriented software model of the Simpletron of Exercise 8.31. Your Simpletron simulator will turn the computer you’re using into a Simpletron, and you’ll actually be able to run, test and debug the SML programs you wrote in Exercise 8.31.

When you run your Simpletron simulator, it should begin by displaying:

*** Welcome to Simpletron! ***
*** Please enter your program one instruction ***
*** ( or data word ) at a time into the input ***
*** text field. I will display the location   ***
*** number and a question mark (?). You then  ***
*** type the word for that location. Enter    ***
*** -99999 to stop entering your program.     ***

Your application should simulate the memory of the Simpletron with one-dimensional array memory of 100 elements. Now assume that the simulator is running, and let’s examine the dialog as we enter the program of Fig. 8.31 (Exercise 8.31):

00 ? +1009
01 ? +1010
02 ? +2009
03 ? +3110
04 ? +4107
05 ? +1109
06 ? +4300
07 ? +1110
08 ? +4300
09 ? +0000
10 ? +0000
11 ? -99999

Your program should display the memory location followed by a question mark. Each of the values to the right of a question mark is input by the user. When the sentinel value -99999 is input, the program should display the following:

*** Program loading completed ***
*** Program execution begins ***

The SML program has now been placed (or loaded) in array memory. Now the Simpletron executes the SML program. Execution begins with the instruction in location 00 and, as in C#, continues sequentially, unless directed to some other part of the program by a transfer of control.

Use variable accumulator to represent the accumulator register. Use variable instructionCounter to keep track of the location in memory that contains the instruction being performed. Use variable operationCode to indicate the operation currently being performed (i.e., the left two digits of the instruction word). Use variable operand to indicate the memory location on which the current instruction operates. Thus, operand is the rightmost two digits of the instruction currently being performed. Do not execute instructions directly from memory. Rather, transfer the next instruction to be performed from memory to a variable called instructionRegister. Then “pick off” the left two digits and place them in operationCode, and “pick off” the right two digits and place them in operand. When the Simpletron begins execution, the special registers are all initialized to zero.

Now, let’s “walk through” execution of the first SML instruction, +1009 in memory location 00. This procedure is called an instruction execution cycle.

The instructionCounter tells us the location of the next instruction to be performed. We fetch the contents of that location from memory by using the C# statement

instructionRegister = memory[ instructionCounter ];

The operation code and the operand are extracted from the instruction register by the statements

operationCode = instructionRegister / 100;
operand = instructionRegister % 100;

Now the Simpletron must determine that the operation code is actually a read (versus a write, a load, or whatever). A switch differentiates among the 12 operations of SML. In the switch statement, the behavior of various SML instructions is simulated as shown in Fig. 8.32. We discuss branch instructions shortly and leave the others to you.

Table 8.32. Behavior of several SML instructions in the Simpletron.

Instruction

Description

read:

Display the prompt "Enter an integer", then input the integer and store it in location memory[ operand ].

load:

accumulator = memory[ operand ];

add:

accumulator += memory[ operand ];

halt:

This instruction displays the message

*** Simpletron execution terminated ***

When the SML program completes execution, the name and contents of each register, as well as the complete contents of memory, should be displayed. Such a printout is often called a memory dump. To help you program your dump method, a sample dump format is shown in Fig. 8.33. A dump after executing a Simpletron program would show the actual values of instructions and data values at the moment execution terminated.

Table 8.33. A sample memory dump.

REGISTERS:
accumulator          +0000
instructionCounter      00
instructionRegister  +0000
operationCode           00
operand                 00

MEMORY:
       0     1     2     3     4     5     6     7     8     9
 0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
10 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
20 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
40 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
50 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
60 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
90 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000

Let’s proceed with the execution of our program’s first instruction—namely, the +1009 in location 00. As we have indicated, the switch statement simulates this task by prompting the user to enter a value, reading the value and storing it in memory location memory[ operand ]. The value is then read into location 09.

At this point, simulation of the first instruction is completed. All that remains is to prepare the Simpletron to execute the next instruction. Since the instruction just performed was not a transfer of control, we need merely increment the instructionCounter.

This action completes the simulated execution of the first instruction. The entire process (i.e., the instruction execution cycle) begins anew with the fetch of the next instruction to execute.

Now let’s consider how the branching instructions—the transfers of control—are simulated. All we need to do is adjust the value in the instruction counter appropriately. Therefore, the unconditional branch instruction (40) is simulated within the switch as

instructionCounter = operand;

The conditional “branch if accumulator is zero” instruction is simulated as

if ( accumulator == 0 )
   instructionCounter = operand;

At this point, you should implement your Simpletron simulator and run each of the SML programs you wrote in Exercise 8.31. If you desire, you may embellish SML with additional features and provide for these features in your simulator.

Your simulator should check for various types of errors. During the program-loading phase, for example, each number the user types into the Simpletron’s memory must be in the range -9999 to +9999. Your simulator should test that each number entered is in this range and, if not, keep prompting the user to re-enter the number until the user enters a correct number.

During the execution phase, your simulator should check for various serious errors, such as attempts to divide by zero, attempts to execute invalid operation codes and accumulator overflows (i.e., arithmetic operations resulting in values larger than +9999 or smaller than -9999). Such serious errors are called fatal errors. When a fatal error is detected, your simulator should display an error message, such as

*** Attempt to divide by zero ***
*** Simpletron execution abnormally terminated ***

and should display a full computer dump in the format we discussed previously. This treatment will help the user locate the error in the program.

8.33

(Project: Simpletron Simulator Modifications) In Exercise 8.32, you wrote a software simulation of a computer that executes programs written in Simpletron Machine Language (SML). In this exercise, we propose several modifications and enhancements to the Simpletron Simulator.

  1. Extend the Simpletron Simulator’s memory to contain 1000 memory locations to enable the Simpletron to handle larger programs.

  2. Allow the simulator to perform remainder calculations. This modification requires an additional SML instruction.

  3. Allow the simulator to perform exponentiation calculations. This modification requires an additional SML instruction.

  4. Modify the simulator to use hexadecimal values rather than integer values to represent SML instructions.

  5. Modify the simulator to allow output of a newline. This modification requires an additional SML instruction.

  6. Modify the simulator to process floating-point values in addition to integer values.

  7. Modify the simulator to handle string input. [Hint: Each Simpletron word can be divided into two groups, each holding a two-digit integer. Each two-digit integer represents the ASCII (see Appendix C) decimal equivalent of an uppercase character. Add a machine-language instruction that will input a string and store the string beginning at a specific Simpletron memory location. The first half of the word at that location will be a count of the number of characters in the string (i.e., the length of the string). Each succeeding half-word contains one ASCII character expressed as two decimal digits. The machine-language instruction converts each character into its ASCII equivalent and assigns it to a half-word.]

  8. Modify the simulator to handle output of uppercase strings stored in the format of Part g. [Hint: Add a machine-language instruction that will display a string beginning at a certain Simpletron memory location. The first half of the word at that location is a count of the number of characters in the string (i.e., the length of the string). Each succeeding half-word contains one ASCII character expressed as two decimal digits. The machine-language instruction checks the length and displays the string by translating each two-digit number into its equivalent character.]

Making a Difference Exercise

8.34

(Polling) The Internet and the web are enabling more people to network, join a cause, voice opinions, and so on. The presidential candidates in 2008 used the Internet intensively to get out their messages and raise money for their campaigns. In this exercise, you’ll write a simple polling program that allows users to rate five social-consciousness issues from 1 (least important) to 10 (most important). Pick five causes that are important to you (for example, political issues, global environmental issues). Use a one-dimensional array topics (of type String) to store the five causes. To summarize the survey responses, use a 5-row, 10-column two-dimensional array responses (of type Integer), each row corresponding to an element in the topics array. When the program runs, it should ask the user to rate each issue. Have your friends and family respond to the survey. Then have the program display a summary of the results, including:

  1. A tabular report with the five topics down the left side and the 10 ratings across the top, listing in each column the number of ratings received for each topic.

  2. To the right of each row, show the average of the ratings for that issue.

  3. Which issue received the highest point total? Display both the issue and the point total.

  4. Which issue received the lowest point total? Display both the issue and the point total.

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

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