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 = newint*[m]; // Allocation of the array of pointers
for (int i = 0; i < m; i++) {
matrix[i] = newint[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 = newint*[n]; // Memory allocation for matrix rows

Next, creating and reading the matrix can be implemented as follows:

// Declaration of a two-dimensional arrayint **A;
// Memory allocation for rows
A = newint*[n];
for (int i = 0; i < n; i++) {
    // Memory allocation for each row (m columns)
    A[i] = newint[m];
    for (int j = 0; j < m; j++) {
        // Input matrix elementscin >> 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 namespacestd;
    
int main() {
    int m, n;
    cin >> m >> n;
    
    // Creating a two-dimensional vectorvector<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

// Write your C++ code here...

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 = newint*[m]; // Allocating an array of pointers (m rows)
for (int i = 0; i < m; i++) {
copy[i] = newint[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
    
Here, the code at the beginning has been modified to allow the user to enter the number of rows and columns.

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.


Transpose matrix AT
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:


intmain(){
{
/* ... previous code */
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.

The task is explained in detail on the website:
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(newint[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(conststd::vector<std::vector<int>>& matrix) {
    for (constauto& 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 indexint index(int i, int j, int n) {
    return i * n + j;
}
// Function to fill the matrixvoid 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 matrixvoid 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.


​Previous
​|<Two-dimensional arrays - matrices in c++
Next
​​Strings in C++ ​>|