Avatar ·

Как объявить двумерный динамический массив в C++ с помощью оператора new?

Привет! Я только начал изучать C++ и столкнулся с проблемой. Нужно создать двумерный массив, размер которого будет известен только во время выполнения программы. Я знаю, как объявить статический двумерный массив: `int arr[3][4];`, но когда я пытаюсь сделать размеры переменными, компилятор ругается. Я пробовал такой код: ```cpp int rows = 5, cols = 10; int arr[rows][cols]; // Ошибка! Нужна константа времени компиляции ``` Почитал про динамическую память и понял, что нужно использовать `new`. Но как правильно выделить память для двумерного массива? Я видел несколько вариантов в интернете, но они все разные. Кто-то использует массив указателей, кто-то выделяет один большой блок памяти. Напишите, пожалуйста, правильный и надёжный способ создать двумерный массив с помощью `new`. Особенно интересует: 1. Как выделить память. 2. Как обращаться к элементам. 3. Как корректно освободить память (чтобы не было утечек). 4. Какой способ считается лучшей практикой в современном C++? Заранее спасибо за помощь! <<>>

Самый распространённый способ: массив указателей на массивы

Это классический подход, который часто используется в C++ и полностью совместим с C. Идея в том, чтобы выделить массив указателей (строки), а затем для каждой строки выделить отдельный массив (столбцы).

#include <iostream>

int main() {
    int rows = 3;
    int cols = 4;

    // 1. Выделяем массив указателей (строки)
    int** arr = new int*[rows];

    // 2. Для каждой строки выделяем массив int (столбцы)
    for (int i = 0; i < rows; ++i) {
        arr[i] = new int[cols];
    }

    // 3. Заполняем массив значениями
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            arr[i][j] = i * cols + j;
        }
    }

    // 4. Выводим массив
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            std::cout << arr[i][j] << " ";
        }
        std::cout << std::endl;
    }

    // 5. ОСВОБОЖДАЕМ ПАМЯТЬ (очень важно!)
    for (int i = 0; i < rows; ++i) {
        delete[] arr[i];  // удаляем каждую строку
    }
    delete[] arr;          // удаляем массив указателей

    return 0;
}

Объяснение:

  • int** arr — это указатель на указатель. Первый уровень (arr[i]) — это строка, второй (arr[i][j]) — элемент.
  • Память выделяется в два этапа: сначала для строк, потом для столбцов.
  • Обращение к элементу: arr[i][j] — работает как с обычным статическим массивом.
  • Освобождение в обратном порядке: сначала удаляем каждую строку, потом массив указателей.

Недостатки этого подхода:

  • Память выделяется не единым блоком, что может снизить производительность из-за кэш-промахов.
  • Требуется больше вызовов new/delete (rows + 1), что увеличивает риск утечек.

Альтернатива: единый блок памяти (рекомендуется для производительности)

Выделяем один большой массив размером rows * cols и эмулируем двумерность через арифметику указателей:

int* arr = new int[rows * cols];

// Доступ к элементу [i][j]:
arr[i * cols + j] = 42;

// Освобождение:
delete[] arr;

Этот способ быстрее, но менее нагляден. Чтобы сохранить синтаксис arr[i][j], можно использовать шаблоны или классы-обёртки.

Современный C++ (C++11 и новее): используйте std::vector

В реальных проектах почти никогда не используют голый new. Лучше взять std::vector:

#include <vector>

std::vector<std::vector<int>> arr(rows, std::vector<int>(cols));

// Или одномерный вектор для производительности:
std::vector<int> arr(rows * cols);
arr[i * cols + j] = 42;

С std::vector память освобождается автоматически, нет риска утечек, и код читается лучше.

<<>>

Использование умных указателей (smart pointers) для автоматического управления памятью

Если вы всё же хотите работать с двумерным массивом через указатели, но избежать ручного вызова delete[], используйте std::unique_ptr (C++11):

#include <memory>

int rows = 5, cols = 10;

// Создаём массив unique_ptr для каждой строки
std::unique_ptr<int[]> arr[rows];
for (int i = 0; i < rows; ++i) {
    arr[i] = std::make_unique<int[]>(cols);
}

// Или одномерный вариант:
auto arr = std::make_unique<int[]>(rows * cols);
arr[i * cols + j] = 42;

Плюсы этого подхода:

  • Память освобождается автоматически при выходе из области видимости.
  • Нет риска забыть вызвать delete[].
  • Код остаётся эффективным (без накладных расходов, как у std::vector).

Минус: синтаксис всё равно требует арифметики индексов для одномерного варианта. Но это можно обернуть в простой класс или структуру.

Итоговая рекомендация: Для учебных задач — используйте int** с ручным управлением, но для реального кода — std::vector или std::unique_ptr.