MyTetra Share
Делитесь знаниями!
Многомерные массивы C++ в динамической памяти через new и delete
Время создания: 05.12.2023 16:06
Текстовые метки: C++, Си++, массив, многомерный, двумерный, двухмерный, трехмерный, new, delete, куча, динамическая, память
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/17017815980kfztvsc3m/text.html на raw.github.com

Создание многомерных массивов в динамической памяти (куче) через ключевое слово new, можно рассмотреть на примере двухмерных массивов.



Работа с массивом в старом Си-стиле


Что такое, по сути, двухмерный массив? Это массив массивов. Соответственно, чтобы создать двухмерный массив в динамической памяти, нам надо создать общий динамический массив указателей (это первое измерение), а затем создать его элементы, которые будут вложенными массивами, так же размещенными в динамической памяти (второе измерение). В общем случае это выглядит так:



#include <iostream>

int main()

{

    unsigned rows = 3;       // Количество строк

    unsigned columns = 2;    // Количество столбцов


// Выделяется память под первое измерение

    int** numbers{new int*[rows]{}};


    // Выделяется память для вложенных массивов (второе измерение)

    for (unsigned i{}; i < rows; i++)

    {

        numbers[i] = new int[columns]{};

    }


...


    // Удаление массивов второго измерения

    for (unsigned i{}; i < rows; i++)

    {

        delete[] numbers[i];

    }


// Удаление массива первого измерения

    delete[] numbers;

}



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



int** numbers{new int*[rows]{}};



Затем в цикле выделяем память для каждого отдельного массива (условно строки массива):



numbers[i] = new int[columns]{};



Освобождение памяти идет в обратном порядке - сначала освобождается память для каждого отдельного вложенного массива, а затем для всего массива указателей.


Пример с вводом и выводом данных двухмерного массива в динамической памяти:



#include <iostream>

int main()

{

    unsigned rows = 3;       // Количество строк

    unsigned columns = 2;    // Количество столбцов


    int** numbers{new int*[rows]{}};  // Память для первого измерения

    for (unsigned i{}; i < rows; i++)

    {

        numbers[i] = new int[columns]{}; // Память для второго измерения

    }


    // Ввод данных для таблицы rows x columns

    for (unsigned i{}; i < rows; i++)

    {

        std::cout << "Enter data for " << (i + 1) << " row" << std::endl;


        // Ввод данных для столбцов i-й строки

        for (unsigned j{}; j < columns; j++)

        {

            std::cout << (j + 1) << " column: ";

            std::cin >> numbers[i][j];

        }

    }


    // Перебор строк

    for (unsigned i{}; i < rows; i++)

    {

        // Вывод данных для столбцов i-й строки

        for (unsigned j{}; j < columns; j++)

        {

            std::cout << numbers[i][j] << "\t";

        }

        std::cout << std::endl;

    }


    for (unsigned i{}; i < rows; i++)

    {

        delete[] numbers[i];

    }

    delete[] numbers;

}



Пример работы программы:



Enter data for 1 row

1 column: 2

2 column: 3

Enter data for 2 row

1 column: 4

2 column: 5

Enter data for 3 row

1 column: 6

2 column: 7


2 3

4 5

6 7



Указатель на массив


Помимо типа int**, который представляет указатель на указатель (pointer-to-pointer), можно использовать тип указателя на массив int (*a)[2] (pointer to array).


Тип int** - это старый стиль работы с массивами, существующий даже в языке Си. При его использовании надо выделять память как для первого измерения, так и для элементов второго измерения. Только элементы "последнего" измерения хранят значения, а все предыдущие измерения являются указателями на массив следующего измерения. Это очень сложная структура, и к тому же работа с ней на уровне машинных кодов весьма неэффективна. Единственное достоинство такой структуры - это фрагментарное размещение элементов (подмассивов) в памяти, благодаря чему нет необходимости выделения одного большого непрерывного куска памяти. Это может иметь значение в системах с сильно ограниченным объемом ОЗУ. На этом достоинства заканчиваются.


В языке Cи++ можно выделить непрерывный кусок памяти под многомерный массив, в котором элементы будут располагаться "вплотную" друг к другу. Размер такого массива будет четко равен количеству хранимых элементов. Никаких указателей внутри массива нет. Для такого выделения памяти для массива из int значений размера n x m используется команда:



new int[n][m]



Вот так просто и красиво. Но язык Си++ не был бы Си++, если б синтаксис не имел ограничения и не использовался абсолютно сумасшедший тип для работы с этим простым и понятным массивом.



Ограничение: переменный размер может иметь только первое измерение. Размеры остальных измерений должны быть константными! То есть, допустимы только следующие конструкции:



// Запрос заранее неизвестного размера первого измерения

size_t n;

std::cout << "Enter first array dimension:";

std::cin >> n;

int (*a)[10] = new int[n][10];

float (*b)[20][30] = new float[n][20][30];


Чтобы работать с "планарным" двумерным массивом размером n x m, необходим указатель a следующего вида:



int (*a)[m]



Где m - это константа. Соответсвенно, тип у указателя будет:



int (*)[m]



Для работы с трехмерными массивами, потребуется, по аналогии, тип int(*)[m][k], где m и k - константы.


Да, в этом типе не видно размера n, не видно в явном виде количества измерений (чтобы пролучить количество измерений, надо взять количество квадратных скобок и увеличить на 1). Но ничего не поделаешь, это C++. Зато доступ к элементам происходит просто: a[i][j].


Чтобы продемонстрировать работу через вышеозначенный тип, можно рассмотреть следующий пример:



#include <iostream>

  

int main()

{

    unsigned n{3}; // Количество строк


// Выделяется память и создается указатель на двумерный массив

    int (*a)[2] = new int[n][2];


    int k{};


    // Установка значений

    for (unsigned i{}; i < n; i++)

    {

        // Установка данных для столбцов i-й строки

        for (unsigned j{}; j < 2; j++)

        {

            a[i][j] = ++k;

        }

    }

 

    // Вывод данных

    for (unsigned i{}; i < n; i++)

    {

        // выводим данные столбцов i-й строки

        for (unsigned j{}; j < 2; j++)

        {

            std::cout << a[i][j] << "\t";

        }

        std::cout << std::endl;

    }

 

    // удаляем данные

    delete[] a;

    a = nullptr;

}



Здесь запись int (*a)[2] представляет указатель на массив из двух элементов типа int. Так фактически можно работать с этим объектом как с двухмерным массивом (таблицей), только количество столбцов в данном случае фиксировано - 2. И память для такого массива была выделена одной командой один раз:



int (*a)[2] = new int[n][2];

...

a[0][1] = 100; // Установка (т. е. запись) значения ячейки массива

cout << a[0][1] << endl; // Чтение значения ячейки массива




То есть в данном случае мы имеем дело с таблицей из n строк и 2 столбцов. Используя два индекса (для строки и столбца), можно обращаться к определенному элементу, установить или получить его значение. Консольный вывод данной программы:



1 2

3 4

5 6


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования