9.14 – Динамическое распределение памяти для массивов

Добавлено 8 июня 2021 в 22:33
Глава 9 – Массивы, строки, указатели и ссылки  (содержание)

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

Чтобы распределить массив динамически, мы используем формы new и delete для массивов (часто называемые new[] и delete[]):

#include <iostream>
#include <cstddef> // std::size_t
 
int main()
{
    std::cout << "Enter a positive integer: ";
    std::size_t length{};
    std::cin >> length;
 
    // Используем new для массива. Обратите внимание, что длина (length)
    // не обязательно должна быть константной!
    int *array{ new int[length]{} }; 
 
    std::cout << "I just allocated an array of integers of length " << length << '\n';
 
    // установить элемент 0 в значение 5
    array[0] = 5; 
 
    // используем delete для массива, чтобы освободить память,
    // используемую для массива
    delete[] array;
 
    // здесь нам не нужно устанавливать массив в nullptr/0,
    // потому что сразу после этого он всё равно выйдет из области видимости
 

    return 0;
}

Поскольку мы распределяем массив, C++ знает, что вместо простой версии new он должен использовать версию new для массива. По сути, вызывается оператор new[], даже если [] не помещается рядом с ключевым словом new.

Значение длины динамически распределяемых массивов должно принадлежать типу, который можно преобразовать в std::size_t. Мы могли бы использовать int, но это вызовет предупреждение компилятора, если тот настроен на высокий уровень предупреждений. У нас есть выбор между использованием std::size_t в качестве типа length или объявлением length как int и последующим приведением его типа при создании массива следующим образом:

int length{};
std::cin >> length;
int *array{ new int[static_cast<std::size_t>(length)]{} };

Обратите внимание: поскольку эта память выделяется из места, отличающегося от памяти, используемой для фиксированных массивов, размер массива может быть довольно большим. Вы можете запустить показанную выше программу и без проблем выделить память для массива длиной 1000000 (или, возможно, даже 100000000). Попробуйте! Поэтому программы, которым необходимо выделить много памяти, в C++ обычно делают это динамически.

Динамическое удаление массивов

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

Это сообщает процессору, что ему необходимо очистить несколько переменных вместо одной. Одна из наиболее распространенных ошибок, которые допускают начинающие программисты при работе с динамическим распределением памяти, – это использование delete вместо delete[] при удалении динамически распределенного массива. Использование простой версии delete для массива приведет к неопределенному поведению, например к повреждению данных, утечкам памяти, сбоям или другим проблемам.

Один из часто задаваемых вопросов о delete[] для массивов: «Откуда при удалении массива известно, сколько памяти нужно удалить?» Ответ заключается в том, что new[] для массивов отслеживает, сколько памяти было выделено переменной, поэтому delete[] для массивов может удалить нужный объем. К сожалению, значение этого размера/длины недоступно программисту.

Динамические массивы почти идентичны фиксированным массивам

В уроке «9.10 – Указатели и массивы» вы узнали, что фиксированный массив содержит адрес памяти первого элемента массива. Вы также узнали, что фиксированный массив может раскладываться на указатель, указывающий на первый элемент массива. В этой разложенной форме длина фиксированного массива недоступна (и, следовательно, размер массива не доступен через sizeof()), но в остальном разница небольшая.

Динамический массив начинает свою жизнь как указатель, указывающий на первый элемент массива. Следовательно, он имеет те же ограничения в том, что он не знает своей длины или размера. Динамический массив работает идентично разложенному фиксированному массиву, за исключением того, что программист отвечает за освобождение динамического массива с помощью ключевого слова delete[].

Инициализация динамически распределяемых массивов

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

int *array{ new int[length]{} };

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

int *array = new int[5];
array[0] = 9;
array[1] = 7;
array[2] = 5;
array[3] = 3;
array[4] = 1;

Это очень раздражает!

Однако, начиная с C++11, теперь можно инициализировать динамические массивы с помощью списков инициализаторов!

// инициализируем фиксированный массив до C++11
int fixedArray[5] = { 9, 7, 5, 3, 1 };

// инициализируем динамический массив, начиная с C++11
int *array{ new int[5]{ 9, 7, 5, 3, 1 } }; 

// Чтобы избежать печати типа дважды, мы можем использовать auto.
// Это часто делается для типов с длинными именами.
auto *array{ new int[5]{ 9, 7, 5, 3, 1 } };

Обратите внимание, что в этом синтаксисе нет оператора = между длиной массива и списком инициализаторов.

Для единообразия фиксированные массивы также можно инициализировать с помощью унифицированной инициализации:

int fixedArray[]{ 9, 7, 5, 3, 1 };    // инициализируем фиксированный массив в C++11
char fixedArray[]{ "Hello, world!" }; // инициализируем фиксированный массив в C ++ 11

Явное указание размера массива необязательно. Но это может помочь в раннем обнаружении ошибок, поскольку компилятор предупредит вас, когда указанная длина меньше фактической.

Изменение размера массивов

Динамическое распределение массива позволяет вам установить длину массива во время выделения. Однако C++ не предоставляет встроенного способа изменения размера уже распределенного массива. Это ограничение можно обойти, динамически распределяя новый массив, копируя в него элементы и удаляя старый массив. Однако это подвержено ошибкам, особенно когда тип элемента является классом (у которого есть особые правила, регулирующие его создание).

Следовательно, мы рекомендуем не делать этого самостоятельно.

К счастью, если вам нужна эта возможность, C++ предоставляет массив с изменяемым размером как часть стандартной библиотеки под названием std::vector. О нем мы скоро поговорим.

Небольшой тест

Вопрос 1

Напишите программу, которая:

std::string поддерживает сравнение строк с помощью операторов сравнения < и >. Вам не нужно выполнять сравнение строк вручную.

Ваш вывод должен соответствовать этому:

How many names would you like to enter? 5
Enter name #1: Jason
Enter name #2: Mark
Enter name #3: Alex
Enter name #4: Chris
Enter name #5: John

Here is your sorted list:
Name #1: Alex
Name #2: Chris
Name #3: Jason
Name #4: John
Name #5: Mark

Напоминание


Вы можете использовать std::getline() для чтения имен, содержащих пробелы.

Напоминание


Чтобы использовать std::sort() с указателем на массив, вычислите начало и конец вручную

std::sort(array, array + arrayLength);

#include <algorithm> // std::sort
#include <cstddef>   // std::size_t
#include <iostream>
#include <limits>    // std::numeric_limits
#include <string>
 
std::size_t getNameCount()
{
  std::cout << "How many names would you like to enter? ";
  std::size_t length{};
  std::cin >> length;
 
  return length;
}
 
// Просим пользователя ввести все имена
void getNames(std::string* names, std::size_t length)
{
  // Игнорировать перевод строки, оставленный std::cin
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
 
  for (std::size_t i{ 0 }; i < length; ++i)
  {
    std::cout << "Enter name #" << i + 1 << ": ";
    std::getline(std::cin, names[i]);
  }
}
 
// Печатает отсортированные имена
void printNames(std::string* names, std::size_t length)
{
  std::cout << "\nHere is your sorted list:\n";
 
  for (std::size_t i{ 0 }; i < length; ++i)
    std::cout << "Name #" << i + 1 << ": " << names[i] << '\n';
}
 
int main()
{
  std::size_t length{ getNameCount() };
 
  // Распределяем массив для хранения имен
  auto* names{ new std::string[length]{} };
 
  getNames(names, length);
 
  // Сортируем массив
  std::sort(names, names + length);
 
  printNames(names, length);
 
  // не забываем использовать delete для массива
  delete[] names;
  // здесь нам не нужно устанавливать names в nullptr/0, потому что он 
  // в любом случае уничтожится сразу после этого.
 
  return 0;
}

Теги

arrayC++ / CppLearnCppnew / deleteВыделение памятиДинамическое распределение памятиДля начинающихМассивОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.