Динамические массивы

Последнее обновление: 24.03.2023

Кроме отдельных динамических объектов в языке C++ мы можем использовать динамические массивы. Для выделения памяти под динамический массив также используется оператор new, после которого в квадратных скобках указывается, сколько массив будет содержать объектов:

int *numbers {new int[4]}; // динамический массив из 4 чисел
// или так
// int *numbers = new int[4];

Причем в этом случае оператор new также возвращает указатель на объект типа int - первый элемент в созданном массиве.

В данном случае определяется массив из четырех элементов типа int, но каждый из них имеет неопределенное значение. Однако мы также можем инициализировать массив значениями:

int *numbers1 {new int[4]{}};	            // массив состоит из чисел 0, 0, 0, 0
int *numbers2 {new int[4]{ 1, 2, 3, 4 }}; // массив состоит из чисел 1, 2, 3, 4
int *numbers3 {new int[4]{ 1, 2 }};       // массив состоит из чисел 1, 2, 0, 0
// аналогичные определения массивов
// int *numbers1 = new int[4]{};	         // массив состоит из чисел 0, 0, 0, 0
// int *numbers1 = new int[4]();	         // массив состоит из чисел 0, 0, 0, 0
// int *numbers2 = new int[4]{ 1, 2, 3, 4 }; // массив состоит из чисел 1, 2, 3, 4
// int *numbers3 = new int[4]{ 1, 2 };       // массив состоит из чисел 1, 2, 0, 0

При инициализации массива конкретными значениями следует учитывать, что если значений в фигурных скобках больше чем длина массива, то оператор new потерпит неудачу и не сможет создать массив. Если переданных значений, наоборот, меньше, то элементы, для которых не предоставлены значения, инициализируются значением по умолчанию.

Стоит отметить, что в стандарт С++20 добавлена возможность выведения размера массива, поэтому, если применяется стандарт С++20, то можно не указывать длину массива:

int *numbers {new int[]{ 1, 2, 3, 4 }}; // массив состоит из чисел 1, 2, 3, 4

После создания динамического массива мы сможем с ним работать по полученному указателю, получать и изменять его элементы:

int *numbers {new int[4]{ 1, 2, 3, 4 }}; 

// получение элементов через синтаксис массивов
std::cout << numbers[0] << std::endl;       // 1
std::cout << numbers[1] << std::endl;       // 2

// получение элементов через операцию разыменования
std::cout << *numbers << std::endl;         // 1
std::cout << *(numbers+1) << std::endl;     // 2

Причем для доступа к элементам динамического массива можно использовать как синтаксис массивов (numbers[0]), так и операцию разыменования (*numbers)

Соответственно для перебора такого массива можно использовать различные способы:

unsigned n{ 5 };  // размер массива
int* p{ new int[n] { 1, 2, 3, 4, 5 } };

// используем индексы
for (unsigned i{}; i < n; i++)
{
    std::cout << p[i] << "\t";
}
std::cout << std::endl;

// добавляем к адресу в указателе смещение
for (unsigned i{}; i < n; i++)
{
    std::cout << *(p+i)<< "\t";
}
std::cout << std::endl;

// проходим по массиву с помощью вспомогательного указателя
for (int* q{ p }; q != p + n; q++)
{
    std::cout << *q << "\t";
}
std::cout << std::endl;

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

Для удаления динамического массива и освобождения его памяти применяется специальная форма оператора delete:

delete [] указатель_на_динамический_массив;

Например:

#include <iostream>

int main()
{
	unsigned n{ 5 };  // размер массива
    int* p{ new int[n] { 1, 2, 3, 4, 5 } };

    // используем индексы
    for (unsigned i{}; i < n; i++)
    {
        std::cout << p[i] << "\t";
    }
	std::cout << std::endl;

	delete [] p;
}

Чтобы после освобождения памяти указатель не хранил старый адрес, также рекомендуется обнулить его:

delete [] p;
p = nullptr;	// обнуляем указатель

Многомерные массивы

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

#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) следует отличать ситуацию "указатель на массив" (pointer to array). Например:

#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];

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

1       2
3       4
5       6
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850