Multi-Dimensional Arrays in C++: Matrix Declaration, Traversal and Functions

6
0

Multi-Dimensional Arrays in C++

A multi-dimensional array organizes data along more than one index. The most common form is a two-dimensional array, which stores elements in rows and columns. This structure is useful for matrices, classroom records, game boards, images, tables, and any problem where values naturally belong to a grid.

Why Do We Need Multi-Dimensional Arrays?

Suppose marks must be stored for three classes, with five students in each class. Creating a separate variable for every student would require fifteen individual variables. One-dimensional arrays improve the design by grouping each class's five marks, but three classes would still require three separately named arrays.

#include <iostream>
using namespace std;
int main() {
int classOne[5] = {99, 90, 89, 78, 78};
int classTwo[5] = {84, 91, 73, 45, 88};
int classThree[5] = {76, 82, 95, 69, 87};
cout << classOne[0] << '\n';
cout << classTwo[3] << '\n';
cout << classThree[2] << '\n';
return 0;
}

This works for three classes, but it does not scale well. If there were one hundred classes, one hundred different array names would be needed. The repeated structure reveals another pattern: every class is represented by an array of five integers. Those related arrays can themselves be placed inside another array, producing a two-dimensional array.

Creating a Two-Dimensional Array

A two-dimensional array can be viewed as an array whose elements are themselves arrays. The declaration int marks[3][5] creates three rows, and each row contains five integers.

#include <iostream>
using namespace std;
int main() {
int marks[3][5] = {
{99, 90, 89, 78, 78},
{84, 91, 73, 45, 88},
{76, 82, 95, 69, 87}
};
cout << marks[1][3] << '\n';
return 0;
}

The first dimension represents the number of rows, while the second represents the number of elements in each row. Every row in this array has capacity for exactly five integers. Built-in arrays are fixed-size, so a sixth value cannot be added to a row after declaring its length as five.

If fewer than five values are supplied for a row during initialization, the remaining elements in that row are initialized to zero. Supplying more values than the declared row capacity causes a compilation error.

Rows, Columns, and Coordinates

A one-dimensional array needs one index because every element lies along one sequence. A two-dimensional array needs two indexes:

  • Row index: Selects which inner array is being accessed.

  • Column index: Selects an element inside that row.

Both indexes are zero-based. For a matrix with R rows and C columns, valid row indexes are 0 through R - 1, and valid column indexes are 0 through C - 1.

Consider the matrix {{10, 20, 30}, {40, 50, 60}}:

  • matrix[0] refers to the complete first row, {10, 20, 30}.

  • matrix[1] refers to the complete second row, {40, 50, 60}.

  • matrix[0][2] selects row 0, column 2, so its value is 30.

  • matrix[1][1] selects row 1, column 1, so its value is 50.

  • In the class-marks example, marks[1][3] accesses 45.

Reading and Updating a Cell

A cell is read or updated in the same way as an element of a one-dimensional array, except that both indexes must be provided. An assignment replaces only the selected cell.

#include <iostream>
using namespace std;
int main() {
int matrix[2][3] = {
{10, 20, 30},
{40, 50, 60}
};
cout << matrix[0][2] << '\n';
matrix[1][1] = 55;
cout << matrix[1][1] << '\n';
return 0;
}

The first output is 30. The assignment changes the value at row 1, column 1 from 50 to 55, so the second output is 55.

Traversing a Matrix

One loop can select each row, but every selected row is itself an array. A second loop is therefore required to visit the columns within that row. The outer loop selects a row, and the inner loop visits every cell in that row.

Trying to print only marks[row] does not print all values in that row. In most expressions, the row array is converted to a pointer to its first element, so streaming it typically displays an address. Each individual cell must be accessed as marks[row][column].

#include <iostream>
using namespace std;
int main() {
int marks[3][5] = {
{99, 90, 89, 78, 78},
{84, 91, 73, 45, 88},
{76, 82, 95, 69, 87}
};
for (int row = 0; row < 3; row++) {
cout << "Row " << row << ": ";
for (int column = 0; column < 5; column++) {
cout << marks[row][column] << ' ';
}
cout << '\n';
}
return 0;
}

For each value of row, the inner loop completes all five column iterations. Only then does the outer loop move to the next row. For a matrix containing R rows and C columns, complete traversal visits R × C cells and takes O(R × C) time.

Taking a Matrix as Input

Matrix input normally begins with the number of rows and columns, followed by the cell values. Two nested loops then visit each valid coordinate and store the corresponding input value.

Built-in array dimensions must be compile-time constants in standard C++. A portable fixed-capacity solution can reserve maximum dimensions and use only the portion described by the input values rows and columns.

#include <iostream>
using namespace std;
constexpr int MAX_ROWS = 100;
constexpr int MAX_COLUMNS = 100;
int main() {
int matrix[MAX_ROWS][MAX_COLUMNS];
int rows, columns;
cin >> rows >> columns;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
cin >> matrix[i][j];
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
cout << matrix[i][j] << ' ';
}
cout << '\n';
}
return 0;
}

The input dimensions must satisfy 0 <= rows && rows <= MAX_ROWS and 0 <= columns && columns <= MAX_COLUMNS. The first pair of nested loops stores the matrix row by row. The second pair prints a newline after every row so that the matrix shape is preserved.

Row-Wise and Column-Wise Traversal

The order of the nested loops determines the traversal order. Consider this 2 × 3 matrix:

{{7, 6, 2}, {1, 2, 1}}

Row-Wise Traversal

For row-wise traversal, the outer loop fixes a row while the inner loop changes the column. The coordinates are visited in this order:

(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)

The resulting values are 7 6 2 1 2 1.

Column-Wise Traversal

For column-wise traversal, the outer loop fixes a column while the inner loop changes the row. The coordinates are visited in this order:

(0, 0), (1, 0), (0, 1), (1, 1), (0, 2), (1, 2)

The resulting values are 7 1 6 2 2 1.

#include <iostream>
using namespace std;
int main() {
int matrix[2][3] = {
{7, 6, 2},
{1, 2, 1}
};
cout << "Row-wise: ";
for (int row = 0; row < 2; row++) {
for (int column = 0; column < 3; column++) {
cout << matrix[row][column] << ' ';
}
}
cout << "\nColumn-wise: ";
for (int column = 0; column < 3; column++) {
for (int row = 0; row < 2; row++) {
cout << matrix[row][column] << ' ';
}
}
return 0;
}

Both traversals visit every cell and therefore take O(R × C) time. The difference is the visiting order: the variable controlled by the outer loop remains fixed while the inner loop completes.

Passing a Matrix to a Function

Passing a built-in two-dimensional array to a function requires special attention. The first array bound may be omitted in the parameter, but the column bound must be known at compile time.

A declaration such as void printMatrix(int matrix[][]) is invalid because the compiler does not know the size of each row. It needs the column count to calculate the address of matrix[i][j]. A runtime variable such as columns cannot be used as that bound in a standard built-in array parameter.

A compile-time constant can define the maximum number of columns. Defining it outside all functions gives it global scope, so it can be used in both the function parameter and main(). Marking it constexpr prevents runtime modification and makes it a valid constant expression.

#include <iostream>
using namespace std;
constexpr int MAX_ROWS = 100;
constexpr int MAX_COLUMNS = 100;
void printMatrix(const int matrix[][MAX_COLUMNS], int rows, int columns) {
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
cout << matrix[row][column] << ' ';
}
cout << '\n';
}
}
int main() {
int matrix[MAX_ROWS][MAX_COLUMNS] = {
{10, 20, 30},
{40, 50, 60}
};
int rows = 2;
int columns = 3;
printMatrix(matrix, rows, columns);
return 0;
}

The first bound is omitted in matrix[][MAX_COLUMNS], but the second is mandatory. The function also receives the actual row and column counts so that it processes only the meaningful portion of the reserved storage. The const qualifier makes the parameter read-only.

Fixed maximum dimensions may reserve cells that are never used. For example, a 2 × 3 matrix stored inside a 100 × 100 array leaves most of the capacity unused. Dynamic structures such as nested vector objects can allocate dimensions at runtime and avoid this fixed-capacity tradeoff, but they use different parameter syntax.

Matrix Boundaries and Shapes

For a matrix with rows rows and columns columns:

  • Valid row indexes range from 0 to rows - 1.

  • Valid column indexes range from 0 to columns - 1.

  • Accessing any coordinate outside these ranges causes undefined behavior for a built-in array.

A matrix does not have to be square. A 2 × 3 matrix has two rows and three columns, so it is rectangular. A square matrix is only the special case in which the number of rows equals the number of columns.

Extending the Idea to Three Dimensions

A two-dimensional array stores multiple one-dimensional arrays. Similarly, a three-dimensional array stores multiple two-dimensional arrays. The idea can be extended to four or more dimensions, although one-dimensional and two-dimensional arrays are the forms used most often in introductory data structures and algorithms.

A three-dimensional element needs three indexes, commonly written as i, j, and k. Complete traversal therefore requires three nested loops.

#include <iostream>
using namespace std;
int main() {
int values[2][2][3] = {
{{1, 2, 3}, {4, 5, 6}},
{{7, 8, 9}, {10, 11, 12}}
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 3; k++) {
cout << values[i][j][k] << ' ';
}
cout << '\n';
}
cout << '\n';
}
return 0;
}

When a built-in three-dimensional array is passed to a function, the first bound may be omitted, but the remaining bounds must be compile-time constants. A parameter can therefore take a form such as int values[][ROWS_PER_LAYER][COLUMNS_PER_ROW].

Summary

  • A two-dimensional array groups same-type values into rows and columns.

  • The declaration type name[rows][columns] fixes both dimensions of a built-in matrix.

  • A cell is accessed with two zero-based indexes: matrix[row][column].

  • Nested loops are required to input, print, update, or otherwise traverse every cell.

  • Row-wise traversal keeps the row fixed in the outer loop; column-wise traversal keeps the column fixed.

  • When passing a built-in matrix to a function, the first bound may be omitted, but the column bound must be a compile-time constant.

  • Matrices may be rectangular or square, and every access must remain within both dimensions.

  • The same array-of-arrays idea extends to three and higher dimensions.

C++

Read Similar Blogs

Comments0