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. By convention, the first identifies the element’s row and the second its column. (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-n array.
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 (i.e., the same number of columns.
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 courses, where the number of exams may vary from course to course.
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—apps also can 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). 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 use 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.
OutputArray
Method OutputArray
is overloaded. The first version (lines 27–42) specifies the array parameter as int[,] array
to indicate that it takes a rectangular array. The second version (lines 45–60) takes a jagged array, because its array parameter is listed as int[][] array
.
OutputArray
for Rectangular ArraysLine 21 invokes method OutputArray
with argument rectangular
, so the version of OutputArray
at lines 27–42 is called. The nested for
statement (lines 32–41) 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). A foreach
statement also can 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.
OutputArray
for Jagged ArraysLine 23 invokes method OutputArray
with argument jagged
, so OutputArray
at lines 45– 60 is called. The nested foreach
statement (lines 50–59) outputs the rows of a jagged array. The inner foreach
statement (lines 53–56) iterates through each element in the current row. 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.
for
StatementsMany 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];
}
}
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.