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 |
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.
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>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.
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.
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; |
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 int
s (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.
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.
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.
This section presents several examples that demonstrate declaring arrays, creating arrays, initializing arrays and manipulating array elements.
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 (0
–4
) 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.
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 |
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.
Assigning a value to a named constant after it’s been initialized is a compilation error.
Attempting to declare a named constant without initializing it is a compilation error.
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.
Defining the size of an array as a constant variable instead of a literal constant makes programs clearer. This technique eliminates so-called magic numbers. For example, repeatedly mentioning the size 10 in array-processing code for a 10-element array gives the number 10 an artificial significance and can be confusing when the program includes other 10s that have nothing to do with the array size.
Often, the elements of an array represent a series of values to be used in a calculation. For example, if the elements of an array represent exam grades, 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 |
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
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 0
s because no students received a grade below 60. Thus, the application displays no asterisks next to the first six grade ranges.
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
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).
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 |
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.
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:
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]
).
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.
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.
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.
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.
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.
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.
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
(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 string
s 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 string
s 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
(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 Card
s in the deck (52).
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 Card
s. 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 string
s—one from the faces
array (which contains the string
s "Ace"
through "King"
) and one from the suits
array (which contains the string
s "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 Card
s 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
Method Shuffle
(lines 30–46) shuffles the Card
s in the deck. The method loops through all 52 Card
s (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.]
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.
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 Card
s in the deck and displays them in four columns of 13 Card
s 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 |
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
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.
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 double
s 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
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
).
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.
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.
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.
This section further evolves class GradeBook
, introduced in Chapter 4 and expanded in Chapters 5–6. 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.
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 int
s) 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 int
s 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.
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
.
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 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 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
.
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.
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.
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.
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
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.
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.
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.
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).
The application in Fig. 8.21 creates an object of class GradeBook
(Fig. 8.20) using the two-dimensional array of int
s 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 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 double
s (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 double
s. Lines 13–14 use the foreach
loop to walk through the array and calculate the total of the double
s 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
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 string
s) 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 string
s 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 string
s 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
.
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.
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.
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.
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
.
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.
The ToString
method of an object is called implicitly in many cases when the object is used where a string
is expected.
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.
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.
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.
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}, ... };
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.
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.
8.6 | Fill in the blanks in each of the following statements:
| ||||||||||||||||||||||||||||||||||||
8.7 | Determine whether each of the following is true or false. If false, explain why.
| ||||||||||||||||||||||||||||||||||||
8.8 | Write C# statements to accomplish each of the following tasks:
| ||||||||||||||||||||||||||||||||||||
8.9 | Consider the tw‘o-by-three rectangular integer array
| ||||||||||||||||||||||||||||||||||||
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. | ||||||||||||||||||||||||||||||||||||
8.11 | (Array Manipulations) Write statements that perform the following one-dimensional-array operations:
| ||||||||||||||||||||||||||||||||||||
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 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 | ||||||||||||||||||||||||||||||||||||
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 | ||||||||||||||||||||||||||||||||||||
8.17 | (Dice Rolling) Write an application to simulate the rolling of two dice. The application should use an object of class | ||||||||||||||||||||||||||||||||||||
8.18 | (Game of Craps) Write an application that runs 1000 games of craps (Fig. 7.8) and answers the following questions:
| ||||||||||||||||||||||||||||||||||||
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: Use a one-dimensional array of type 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 | ||||||||||||||||||||||||||||||||||||
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:
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 | ||||||||||||||||||||||||||||||||||||
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 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 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).
| ||||||||||||||||||||||||||||||||||||
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.
| ||||||||||||||||||||||||||||||||||||
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 | ||||||||||||||||||||||||||||||||||||
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.
| ||||||||||||||||||||||||||||||||||||
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:
When this process completes, the array elements that are still | ||||||||||||||||||||||||||||||||||||
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.
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 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 | ||||||||||||||||||||||||||||||||||||
8.29 | (Card Shuffling and Dealing) Modify the application of Fig. 8.11 to deal a five-card poker hand. Then modify class
[Hint: Add methods | ||||||||||||||||||||||||||||||||||||
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. |
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 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 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.
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 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 Now write SML programs to accomplish each of the following tasks:
| ||||||||||||||||||||||||||||||||||
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 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 *** Program loading completed *** *** Program execution begins *** The SML program has now been placed (or loaded) in array Use variable Now, let’s “walk through” execution of the first SML instruction, The 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 Table 8.32. Behavior of several SML instructions in the Simpletron.
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.
Let’s proceed with the execution of our program’s first instruction—namely, the 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 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 ( 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 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 *** 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.
|