Week 10

2D Arrays

 012
0123
1456
2789
3101112

A two-dimensional array (2D) is a block of memory, visualized as a table with rows and columns. In C++ this is implemented (under the hood) by creating a 1D array whose elements are, themselves, also 1D arrays.

Examine this code:

 012
05193
122-810
int a2d[2][3]; // a2d is a 2D array

a2d[0][0] = 5;
a2d[0][1] = 19;
a2d[0][2] = 3;
a2d[1][0] = 22;
a2d[1][1] = -8;
a2d[1][2] = 10;

It is easiest to think of a2d as containing two rows and three columns: a 2 × 3 array. To access an element of a 2D array, use two subscripts. The first is the row, the second is the column. Both are zero-based. On line 6, for instance, the first index (here 1) signifies the second row and the second index (here 0) denotes the column within the array.

Week 10

Row Major Order

While conceptually the array a2d contains rows and columns, physically the elements are stored linearly, with the elements of each row following the elements of the preceding row in memory.

The array a2d actually contains two elements (not 6!). Each is a one-dimensional int array of size 3. This is how the compiler sees the declaration:

 a2d[0] a2dh[1]
a2d =5193 22-810
 [0][0][0][1][0][2][1][0][1][1][1][2]

That means, instead of using a 2D array, we could store the same elements in a 1D array, here named a, like this:

 [0][1][2][3][4][5]
a =5193 22-810

To treat this 1D array as a 2D array, (as you've done with all of your image projects in this class), you need to recall the formula for array access expressions:

a[offset] = *(a + offset)

You can convert this to where a 2D array offset expression like this:

a[row, col] = *(a + (row * row - width + col))

Notice how similar this is to 1D pointer-address arithmetic; the only new addition is the expression row * row-width to the calculation.

Week 10

2D Array Initialization

The 2D array a2d could be declared and initialized like this:

int a2d[2][3] = {
   {5, 19, 3},
   {22, -8, 10}
};

Each row appears is in its own set of curly braces. Because, the array is actually laid out in a linear fashion, you may omit the inner braces all together, but that is not as clear:

int a2d[2][3] = {5, 19, 3, 22, -8, 10};

The rules for partial initialization are similar to 1D arrays: any uninitialized elements are value initialized to 0. With embedded braces, partial initialization is on a row-by-row basis; if you omit them, the rows are ignored. These examples use the same initial values, but produce quite different results.

 012
0500
122-80
int a2d[2][3] = {
   {5},
   {22, -8}
};
 012
0522-8
1000
int a2d[2][3] ={ 
    5, 22, -8
};

When initializing, you may omit the first explicit dimension, but the second dimension [3] is required. The compiler must know how big each element of a2d is.

int a2d[][3] = {
    {5, 19, 3}, 
    {22, -8, 10}
};
Week 10

2D Arrays & Functions

Pass 2D arrays to functions by address, just like 1D arrays. The following function prints the contents of a ROWS × COLS 2D array of double:

void print(const double m[ROWS][COLS])
{
    for (size_t row = 0; row < ROWS; ++row)
   { 
        for (size_t col = 0; col < COLS; col++)
            cout << setw(5) << m[row][col];
        cout << endl;   // end of each row
   } 
}

Of course, this function is really quite limited since it can only be used to process an array that is exactly ROWS x COLS elements in size.

You can make it a little more flexible by omitting the first dimension, and then passing the number of rows as a parameter. You cannot, however, leave off the number of columns. That must be a constant.

void print(const double m[][COLS], size_t nRows)
{
    for (size_t row = 0; row < nRows; ++row)
   { 
      ...
   } 
}

This inflexibility is one of the reasons that the built-in 2D arrays are so limiting in C/C++.

An expression that uses just one subscript with a 2D array represents a single row within the 2D array. This row is itself a 1D array. Thus, if a2d is a 2D array and i is an integer, then the expression a2d[i] is a 1D array representing row i.