Two Dimensional Arrays in C++
What Is a Two Dimensional Array in C++
A two dimensional array is an array accessed using two indices: arr[i][j].
The data is interpreted as rows and columns, similar to a table or matrix.
Implementation
There are multiple ways to implement a two dimensional array in C++. The main difference between them is how the data is laid out in memory. Before looking at the implementations, it is important to understand contiguous vs non‑contiguous memory layouts.
Contiguous memory (one big block)
- Better spatial locality. Elements next to each other are more likely to be cached by the CPU.
- Easier to copy using a single
memcpy. - More compact. No extra pointer overhead.
Non‑contiguous memory (pointer to pointer)
- Each row is allocated separately and can be scattered across memory.
- Worse cache performance.
- Extra indirection. Accessing
arr[i][j]means:- Read the pointer at
arr[i]. - Access the value at
arr[i][j].
- Read the pointer at
- Harder to free correctly.
- Main advantage is flexibility. Each row can have a different length.
Note: Contiguous vs non‑contiguous describes how data is stored in memory, not the syntax.
- Static arrays: contiguous
- Pointer to pointer: non‑contiguous
- Dynamic single block: contiguous
1. Static Two Dimensional Arrays
This is the simplest way to define a two dimensional array. The dimensions must be known at compile time.
#include <iostream>
using namespace std;
int main() {
int arr[3][5];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
cin >> arr[i][j];
}
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
cout << arr[i][j] << " ";
}
cout << "\n";
}
return 0;
}
Memory layout: Single contiguous block.
Advantage: Simple and easy to use.
Disadvantage: Fixed size and cannot be resized.
2. Pointer to Pointer (Dynamic Jagged Arrays)
This approach allocates an array of pointers, where each pointer represents a row.
#include <iostream>
using namespace std;
int main() {
int n = 0, m = 0;
cin >> n >> m;
int** arr = new int*[n];
for (int i = 0; i < n; i++) {
arr[i] = new int[m];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> arr[i][j];
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << arr[i][j] << " ";
}
cout << "\n";
}
for (int i = 0; i < n; i++) {
delete[] arr[i];
}
delete[] arr;
return 0;
}
Memory layout: Non‑contiguous. Each row can be stored in a different location.
Advantage: Dimensions do not need to be known at compile time. Can support jagged arrays.
Disadvantage: Slower access, possible fragmentation, and error‑prone memory management.
3. Dynamic Two Dimensional Arrays (Single Block)
Here, all elements are stored in one contiguous block. Indexing is done manually using this formula:
arr[i][j] = arr[i * number_of_cols + j]
#include <iostream>
using namespace std;
int main() {
int n = 0, m = 0;
cin >> n >> m;
int* arr = new int[n * m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> arr[i * m + j];
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << arr[i * m + j] << " ";
}
cout << "\n";
}
delete[] arr;
return 0;
}
Memory layout: Single contiguous block.
Advantage: Dynamic size with good cache performance.
Disadvantage: Manual index calculations make the code less readable.
Conclusion
All three approaches are valid, but they solve different problems. Static arrays are best when sizes are fixed and known early. Pointer‑to‑pointer arrays trade performance for flexibility. A single dynamic block gives the best memory locality but costs readability. Understanding how memory is laid out is more important than memorizing syntax. Once that part is clear, choosing the right approach becomes straightforward.