TWO-DIMENSIONAL DYNAMIC ARRAYS - MATRICES IN C++ PROGRAMMING LANGUAGE
Page Contents
Welcome to the page dedicated to two-dimensional and multi-dimensional arrays in C++! This page provides all the key information, code examples, and practical tips on working with matrices and multi-dimensional data structures. Using the table of contents, you can quickly jump to interesting sections and find the content that interests you.
Introduction: Static and Dynamic Arrays in C++
In programming, especially in C++, working with arrays is common and necessary when it is important to organize data in a structured way. Arrays can be either static or dynamically allocated, and understanding the differences between them is crucial for efficient memory management.
Static Arrays
Static arrays are arrays whose size is determined at compile time and cannot be changed during program execution. They are declared as follows:
int matrix[3][3]; // Declaration of a static 3x3 two-dimensional array
Characteristics of Static Arrays:
- Memory is allocated on the stack, allowing for fast allocation and deallocation.
- The size of the array must be known at compile time and cannot be adjusted dynamically.
- Memory is automatically freed when the array goes out of scope.
- If the array is too large, it can cause a stack overflow.
Dynamic Arrays
Dynamic arrays are arrays whose size can be defined at runtime. They are allocated on the heap, and memory management is done explicitly using the new and delete operators.
Example: Creating a two-dimensional dynamic array of size m x n:
int** matrix = new int*[m]; // Allocation of the array of pointers
for (int i = 0; i < m; i++) {
matrix[i] = new int[n]; // Allocation of each row
}
Memory must be explicitly freed:
for (int i = 0; i < m; i++) {
delete[] matrix[i]; // Deleting each row
}
delete[] matrix; // Deleting the array of pointers
Advantages of Dynamic Arrays:
- Flexibility – The size of the array can be defined at runtime.
- Efficient memory usage – Only the required amount of memory is allocated.
- Large data structures – Allows the creation of arrays that exceed stack capacity.
Disadvantages of Dynamic Arrays:
- Increased complexity – The programmer is responsible for properly freeing memory to avoid memory leaks.
- Slower allocation – Operations on the heap are slower than operations on the stack.
When to Use Static vs. Dynamic Arrays
Use static arrays when you know the exact size of the array at compile time and want simpler, faster code.
Use dynamic arrays when the array size depends on user input or other factors during runtime.
Modern Approach: Using std::vector
Although the example above demonstrates dynamic memory allocation using pointers, in modern C++ it is recommended to use std::vector
for dynamic arrays. This approach is more in the spirit of C++ because it provides automatic memory management, better safety, and simpler syntax.
Below is an alternative implementation using std::vector
:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int m, n;
cin >> m >> n;
// Creating a two-dimensional vector
vector<vector<int>> matrix(m, vector<int>(n));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cin >> matrix[i][j];
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
This vector-based approach provides automatic memory management and simplifies the syntax compared to manual pointer manipulation, making it more aligned with modern C++ practices.
Introduction to Dynamic Matrix Allocation
In the previous article "Two-Dimensional Arrays – Matrices", we explained how to work with static arrays, where memory is allocated in advance and the matrix dimensions (m×n) remain unchanged during program execution. In this article, we will describe how to allocate memory dynamically using pointers, allowing the matrix dimensions to be changed later.
Example: Dynamic Array with Resizable Dimensions
Suppose we want to read a matrix of integers with dimensions 4×3, where the dimensions (number of rows and columns) can be changed during program execution. This can be done using pointers to integer data (int*
), where each row is a dynamic array, and the entire matrix is an array of pointers, i.e., a pointer to a pointer (e.g., int** A
).
It is important to note that defining these pointers—and thus dynamic arrays and matrices—differs in C and C++ due to the introduction of the new
and delete
operators in C++. In C++, dynamic memory allocation is handled by the new
operator, so the pointer A
is created as follows:
int **A;
// Declaration of a pointer to a pointer (dynamic matrix)
A = new int*[n]; // Memory allocation for matrix rows
Next, creating and reading the matrix can be implemented as follows:
// Declaration of a two-dimensional array
int **A;
// Memory allocation for rows
A = new int*[n];
for (int i = 0; i < n; i++) {
// Memory allocation for each row (m columns)
A[i] = new int[m];
for (int j = 0; j < m; j++) {
// Input matrix elements
cin >> A[i][j];
}
}
To print the entered matrix, a nested for
loop is used:
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << A[i][j] << " ";
}
cout << endl;
}
Note on Memory Deallocation:
After using a dynamically allocated matrix, it is important to free the memory to avoid memory leaks:
for (int i = 0; i < n; i++) {
delete[] A[i]; // Deleting each row
}
delete[] A; // Deleting the array of pointers
Advantages of Dynamic Arrays:
- Flexibility – the array size can be defined at runtime.
- Efficient memory usage – only the necessary amount of memory is allocated.
- Large data structures – allows creating arrays that exceed stack capacity.
Disadvantages of Dynamic Arrays:
- Increased complexity – the programmer must manually free memory to avoid memory leaks.
- Slower allocation – heap operations are slower compared to stack operations.
When to Use Static vs. Dynamic Arrays?
- Static Arrays: Use them when the matrix size is known in advance, resulting in simpler and faster code.
- Dynamic Arrays: Use them when the matrix size depends on user input or other runtime factors.
Modern Approach: Using std::vector
Although the above example demonstrates dynamic memory allocation using pointers, a better approach in modern C++ is to use std::vector
. This method aligns more with modern C++ practices, as it provides automatic memory management, improved safety, and simpler code.
Below is an equivalent solution using std::vector
:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int m, n;
cin >> m >> n;
// Creating a two-dimensional vector
vector<vector<int>> matrix(m, vector<int>(n));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cin >> matrix[i][j];
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
Using std::vector
provides automatic memory management, which eliminates the need for manual deallocation and makes the code safer and easier to maintain. This approach is more in line with modern C++ practices.
Printing matrices in C++
To print a matrix in C++, a nested for loop is used. For example, if we have a matrix a with dimensions m×n, the code for printing it may look like this:
cout << "Matrix output:" << endl;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cout << a[i][j] << " ";
}
cout << endl;
}
After running the program and entering the data, the matrix will be displayed in the console.
Matrix Output:
1 2 3 4 5 6 7 8 9
Test your code in the editor below
Copying a Matrix in C++
To copy the elements of a dynamically allocated matrix, we will create a special function that takes a pointer to the matrix and dimensions m and n as parameters. By passing a pointer to the matrix, we ensure that any changes made within the function reflect on the original matrix. Copying is done by transferring data into a new matrix, which is then returned as a result.
int **copyMatrix(int **original, int m, int n) {
int **copy;
copy = new int*[m]; // Allocating an array of pointers (m rows)
for (int i = 0; i < m; i++) {
copy[i] = new int[n]; // Allocating memory for each row (n columns)
for (int j = 0; j < n; j++) {
copy[i][j] = original[i][j]; // Copying values
}
}
return copy; // Returns the copied matrix
}
Since the matrix is printed multiple times, it is recommended to create a separate printing function that takes a pointer to the matrix and dimensions m and n as parameters.
// Function for printing a matrix
void printMatrix(int **matrix, int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
In the main function, a new matrix is created by calling the copy function and then printed:
int **B;
B = copyMatrix(A, m, n);
printMatrix(B, m, n);
After running the application, the result of copying the matrix elements will be displayed.
Matrix A (original):
1 2 3 4 5 6 7 8 9
Matrix B (copied):
1 2 3 4 5 6 7 8 9
Transpose Matrix
The transpose of a matrix is obtained by swapping the rows and columns of the original matrix. This process is often useful, for example, when determining the inverse of a matrix.
Inverse matrix A-1
The transpose of a matrix (AT) can be determined using a dedicated function, as shown in the following code:
void transpose(int **a, int n) {
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
// Swap elements: a[i][j] is swapped with a[j][i]
int temp = a[i][j];
a[i][j] = a[j][i];
a[j][i] = temp;
}
cout << endl;
}
}
Note: In the inner for loop, the iteration starts from j = i because the elements in columns where j ≤ i have already been swapped.
The function transpose() is called in the main() function:
{
transpose(A, n); // calling the transpose function
cout << "Transposed matrix" << endl;
printMatrix(A, n);
Calculating the Determinant of a Square Matrix
Below is an example where the size of a square matrix is entered, followed by its elements. The function that calculates the determinant uses a recursive algorithm.
Input example:
3
3 5 6
2 -6 3
0 1 7
Output example:
-193
This problem explains in detail the concept of recursive algorithms for calculating the determinant, which can be useful for a deeper understanding of algebraic operations on matrices.
Recursive Algorithms
Code Modernization in Newer Versions of C++
Modern C++ introduces numerous features that make code more concise, readable, and safer. While the previous examples demonstrate the traditional way of working with dynamic matrices, the following section provides insight into some of the most significant improvements you can apply using modern standards:
Range-based for loops
Instead of classic for loops that use indexing, range-based for loops allow direct iteration through container elements, simplifying the code and making it more readable.
Example:
for (const auto& element : container) {
cout << element << endl;
}
Smart pointers
The use of smart pointers (such as std::unique_ptr and std::shared_ptr) reduces the risk of memory leaks, as object lifecycle management is handled automatically.
Example:
std::unique_ptr<int[]> array(new int[size]);
Initializer lists
Initializing containers using initializer lists simplifies the code and provides a more readable syntax.
Example:
std::vector<int> v = {1, 2, 3, 4, 5};
Lambda expressions
Lambda expressions allow defining anonymous functions directly at the place of use. This is particularly useful for writing custom comparators for sorting or other operations requiring short functions.
Example:
sort(v.begin(), v.end(), [](const auto& a, const auto& b) {
return a > b;
});
By implementing these modern techniques, code examples can be not only updated but also aligned with best practices in contemporary C++ programming. This addition provides users with guidelines for improving their code without modifying existing examples.
In this example, we demonstrate how to use modern approaches for working with dynamic two-dimensional arrays in C++.
1️⃣ Using std::vector
for dynamic two-dimensional arrays
We use std::vector
to simplify memory management and avoid manual allocations.
// Including libraries
#include <iostream>
#include <vector>
void fillMatrix(std::vector<std::vector<int>>& matrix, int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = i * n + j + 1;
}
}
}
void displayMatrix(const std::vector<std::vector<int>>& matrix) {
for (const auto& row : matrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
}
int main() {
int m = 3, n = 4;
std::vector<std::vector<int>> matrix(m, std::vector<int>(n));
fillMatrix(matrix, m, n);
displayMatrix(matrix);
return 0;
}
2️⃣ Using std::unique_ptr
for dynamic arrays
We use smart pointers to automatically manage memory.
// Including libraries
#include <iostream>
#include <memory>
int main() {
int m = 3, n = 4;
std::unique_ptr<std::unique_ptr<int[]>[]> matrix = std::make_unique<std::unique_ptr<int[]>[]>(m);
for (int i = 0; i < m; i++) {
matrix[i] = std::make_unique<int[]>(n);
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = i * n + j + 1;
std::cout << matrix[i][j] << " ";
}
std::cout << std::endl;
}
return 0;
}
✅ Conclusion
The best choice depends on the use case:
- std::vector - The easiest and safest for most situations.
- std::unique_ptr - A safer way to manually manage memory.
- std::array - The fastest for fixed-size static arrays.
Smart Pointers
Smart pointers (smart pointers) in C++, such as std::unique_ptr, std::shared_ptr, and std::weak_ptr, are used for automatic management of dynamically allocated memory. Their main advantage is that memory is automatically released when the object is no longer in use, reducing the risk of memory leaks and other errors related to manual memory management (new and delete).
When and why should you use smart pointers?
Automatic memory release: Smart pointers use the RAII (Resource Acquisition Is Initialization) principle to ensure that memory is freed as soon as the object goes out of scope.
Safety: They help prevent errors such as double deletion or memory leaks.
Code simplicity: The code becomes more readable and easier to maintain because you don't have to manually track when to free memory.
Example of using smart pointers for a dynamic matrix (second example)
In this example, we will create a two-dimensional matrix as a single continuous memory block, which can be more efficient in terms of memory access. We will use std::unique_ptr with a pointer-to-array type (int[]).
Explanation:
- We allocate a single memory block of size m * n.
- We create a helper function index, which calculates the corresponding index in a one-dimensional array from two-dimensional coordinates (i, j).
- We use these coordinates to access matrix elements.
// Function to convert 2D index to 1D index
int index(int i, int j, int n) {
return i * n + j;
}
// Function to fill the matrix
void fillMatrix(int* matrix, int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
matrix[index(i, j, n)] = i + j; // Example: sum of row and column indices
}
}
}
// Function to display the matrix
void displayMatrix(int* matrix, int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
std::cout << matrix[index(i, j, n)] << " ";
}
std::cout << endl;
}
}
int main() {
int m = 3, n = 4;
// Creating a smart pointer for a continuous memory block
std::unique_ptr<int[]> matrix = std::make_unique<int[]>(m * n);
fillMatrix(matrix.get(), m, n);
displayMatrix(matrix.get(), m, n);
return 0;
}
In this example, the std::make_unique function allocates memory for m * n elements of type int. The index function is used to convert two-dimensional coordinates into the corresponding index in a one-dimensional array. This approach ensures more efficient memory access, and the smart pointer automatically releases memory when matrix goes out of scope.
Additional explanation:
Why use smart pointers?
They automatically manage memory, reducing the chance of errors such as memory leaks.
The code becomes safer and easier to maintain since there is no need for manual delete[] calls.
When should you use this approach?
When working with large dynamic data structures or complex objects and you want to focus only on application logic rather than memory management.
Next
Strings in C++ >| |