Introduction to Multi-dimensional Arrays
Learn how multi-dimensional arrays can be created, initialized, and accessed.
Multi-dimensional arrays
A multi-dimensional array can be seen as an “array of arrays.”
A -dimensional array is an array of -dimensional arrays, in which we can use nested braces to group the different levels of elements.
The most commonly used multi-dimensional array is a 2-D (two-dimensional) array. It can be visualized as an array of single-dimensional arrays. Let’s see how to make it in C++.
Creating a two-dimensional array
A 2-D array can be represented as rows and columns. The following syntax is used to declare a 2-D array.
DataType array_name[row_size][column_size];
For example:
int TwoDArray[4][3];
Here, a 2-D array named TwoDArray
is being declared with 4 rows and 3 columns. It can be visualized as an array having 4 elements and each element is a single dimension array of size 3; so in total, it contains 12 values.
The TwoDArray
array may be viewed logically as the following:
Note: We will shortly discuss how these two-dimensional arrays are stored and how the compiler does the exact mapping of a two-dimensional array to real physical memory.
Initializing a 2-D array
A 2-D array can be initialized in a number of ways.
Using nested braces
Since a 2-D array is an array of 1-D arrays, we can use nested curly braces to group the different levels of elements.
For instance, in the example below, we have a 2-D array of 5 rows and 3 columns. We can see five elements inside the outer curly braces, which can be viewed as rows. Each of these row elements contains three elements that can be viewed as the columns that are being initialized.
int TwoDArray[5][3] = { {0,1,2}, {1,6,4}, {2,9,5}, {3,9,6}, {4,9,4}};
Initializing within a single pair of braces
Can you guess how the arrays will be initialized with the following expression?
int TwoDArray[3][2]={42, 81, 72};
Here, the first three indices of the 2-D array will be initialized with 42, 81, and 72, while the remaining three indices will be initialized with 0. So the first row will be {42, 81}, the second row will be {72, 0}, and the third row will be {0,0}.
Though this expression does not have the nested curly braces for the different dimensions, the initialization using this expression is also valid.
This is possible because the compiler stores a multi-dimensional array in the form of a 1-D array at the backend.
As long as the number of values does not exceed the size of the array, all values are assigned in the increasing subscript of the last dimension by the compiler. We will be able to understand this better when we see how array elements can be accessed in the lesson below.
Initializing with zeros
Like in a one-dimensional array, when we don’t explicitly initialize the array or write anything within the braces, the elements are initialized with 0s. For example:
int TwoDArray[5][3] = { {0,1,2}, {1, 6}, {2}, {3,9,6}, {} };
Here, the second row’s third column will be initialized to 0, and the third row’s second and third columns will also be initialized to 0. In the fifth row, all three columns will be initialized to 0.
In short, the elements not explicitly initialized are initialized to zero.
What about the following expression?
int TwoDArray[5][3] = { {0}, {1}, {2}, {3}, {4} };
Here, the first column of every row will be initialized to 0, 1, 2, 3, and 4, respectively. The rest will all be initialized to 0.
Click “Run” to see the output of this example.
#include <iostream>using namespace std;int main() {int TwoDArray[5][3] = { {0}, {1}, {2}, {3}, {4} };// printing the matrixfor(int r=0; r<5; r++) // for each row{for(int c=0; c<3; c++) // for each column{cout << TwoDArray[r][c] << " "; // printing the values}cout << endl;}return 0;}
Explicitly initializing certain elements
Like in a simple array, we can also explicitly initialize certain indices. For example, in the TwoDArray
matrix, we can initialize the following indices like this:
int TwoDArray[4][3]={};// this will initialize the entire array with value = 0
// second row, third column
TwoDArray[1][2] = 3;
// first row, first column
TwoDArray[0][0] = 9;
The matrix will be initialized as in the image shown below.
Based on the TwoDArray
matrix shown in the image above, solve the quiz below.
What will be the result of the expression TwoDArray[0][5] = 6;
?
It’s a logical error.
Value 3 will be replaced by 6 in the second row, third column.
Below are some example programs based on what we learned above. Let’s run all programs one by one to see the outputs.
Initializing a 2-D array with user input
Below is a program that takes numbers as input from the user to initialize two matrices, adds the result inside a new matrix, and prints the result.
#include <iostream> using namespace std; int main() { const int rows = 2; const int cols = 2; int matrixA[rows][cols], matrixB[rows][cols], result[rows][cols]; cout << "Enter 4 integer numbers for a 2x2 matrix A = " << endl; for(int r=0; r<rows; r++) { for(int c=0; c<cols; c++) { cin >> matrixA[r][c]; } } cout << "Enter 4 integer numbers for a 2x2 matrix B = " << endl; for(int r=0; r<rows; r++) { for(int c=0; c<cols; c++) { cin >> matrixB[r][c]; } } cout << endl; // storing the sum of the matrices in result[][] matrix for(int r=0; r<rows; r++) { for(int c=0; c < cols; c++) { result[r][c] = matrixA[r][c] + matrixB[r][c]; } } cout << endl; // printing the result[][] matrix cout << "A+B= " << endl; for(int r=0; r<rows; r++) { for(int c=0; c<cols; c++) { cout << result[r][c] << " "; } cout << endl; } return 0; }
Accessing and mapping functions
Though we represent a 2-D array in terms of rows and columns, a multidimensional array is practically stored like a 1-D array. So the memory allocation is linear.
Say we have a 2-D array with rows and columns; then, at the back end, this array will be stored in the form of a 1-D array with a total of locations (based on the array data type).
Let’s see how multi-dimensional arrays are mapped to a single-dimensional array. There are two ways to map the elements:
- Row major mapping
- Column major mapping
Since C++ follows the row-major layout, let’s look at row-major mapping.
The general formula that the compiler uses is:
baseAddress + * cols +
Where baseAddress is the address of the array, cols is the number of columns of the array, and and are the rows and column indices, respectively, of the element that we want to access or initialize.
Say we wanted to access the element at [1][2]
, which is in the arr
array.
The compiler does this via the following steps:
-
Take the base address of array
arr
. -
Take the product of the row index
ri
with the number of columns, i.e.cols
which is equal tori*cols
(e.g. for ri=1, it will be ). This is like skipping rows. -
Add the outcome to the column index i.e. (4 + 2 = 6).
-
Multiply the result by the size of the data type (which is 4 bytes for integers), i.e. , which is equivalent to 0x0018 in hexadecimal.
-
Finally, add the above result to the array base address (arr + 0x0018)
Since the base address is 0xb0, so 0xb0 + 0x0018 = 0xc8, which is exactly where value 5 is in our arrays.
Passing a multidimensional array to a function
We can pass a multidimensional array to a function like a simple 1D array.
To pass an array to a function, when passing the parameters in the function definitions, we write the name of the array along with two square brackets (e.g. int Array[][maxCols]
) to tell the compiler that it's a 2-D array.
For the remainder of this chapter, we have made the following two auxiliary functions for 2-D arrays:
The load2DArray
function
This function reads a matrix from the passed ifstream
into the passed Array
and also sets the rows
and columns
accordingly.
void load2DArray(ifstream &rdr, int Array[][maxCols], int &rows, int &cols){rdr >> rows >> cols;for (int r = 0; r < rows; r++){for (int c = 0; c < cols; c++){rdr >> Array[r][c];}}}
The print2DArray
function
This function prints the char array MSG
and then prints out the passed Array
.
void print2DArray(const char MSG[], int Array[][maxCols], int rows, int cols){cout << endl << MSG << endl;for (int r = 0; r < rows; r++){cout << "\t\t";for (int c = 0; c < cols; c++){cout << setw(3) << Array[r][c];}cout << endl;}}
When we call the functions, we only write the array's name as the argument to pass to the entire array, as shown in the code below:
#include <iostream>#include <iomanip>#include <fstream>using namespace std;#include <math.h>#define maxRows 100 // maxRows is a macro will be replaced by 100#define maxCols 100 // maxCols is a macro will be replaced by 100void load2DArray(ifstream &rdr, int Array[][maxCols], int &rows, int &cols);void print2DArray(const char MSG[], int Array[][maxCols], int rows, int cols);int main(){int Matrix1[maxRows][maxCols], r1, c1;ifstream rdr("Matrix.txt");load2DArray(rdr, Matrix1, r1, c1);print2DArray("Matrix 1:", Matrix1, r1, c1);return 0;}
Notice that the parameter int Array[][maxCols]
in the function definition has the number of columns, or maxCols
, specified but not the number of rows.
It is not necessary (or even recommended) to specify the number of rows. Even if we mention the total rows, the compiler ignores it altogether, just like in the case of a single-dimension array. Still, the number of columns should always be specified. Can you tell why this is so?
In the code above, we have a file called "Matrix.txt" in which we have a 5x6
matrix. We read the matrix values from this file with the help of the fstream
header file. The fstream
library allows us to handle files in C++. We can create, read, and write inside a file using this header file.
But before we do that, recall that we used to set a maximum capacity for the array, which was more than the array size. We've done the same here, except we've used macros (in lines 6 and 7) to write maxRows
and maxCols
. Wherever a macro name (maxRows
or maxCols
) is used within the code, the compiler replaces it with the macro definition (i.e., 100
here). The values of macros cannot be changed during runtime because they are not variables; they are constants. We use the #define
directive to define macros.