C++ Динамическая память

Понимание того, как работает динамическая память в C++, необходимо для того, чтобы стать квалифицированным программистом C++. Память в программе на C++ делится на две части:

  • Стек:Все переменные, объявленные внутри функции, будут занимать стековую память.
  • Куча:Это память, не используемая программой, которая может применяться для динамического выделения памяти во время выполнения программы.

Часто невозможно заранее предсказать, сколько памяти потребуется для хранения конкретной информации в определённой переменной; требуемый размер памяти можно определить только во время выполнения.

В C++ можно использовать специальные операторы для выделения памяти в куче для переменных заданного типа во время выполнения, что возвращает адрес выделенного пространства. Эти операторы — этоnew-операторы.

Если динамически выделенное пространство памяти больше не нужно, можно использоватьdelete-оператор, чтобы удалить память, ранее выделенную оператором new.

Операторы new и delete

Ниже приведён общий синтаксис использования оператора new для динамического выделения памяти под любой тип данных:

new data-type;

Здесьdata-typeможет быть любым встроенным типом данных, включая массивы, или любым пользовательским типом данных, включая классы или структуры. Сначала рассмотрим встроенные типы данных. Например, мы можем определить указатель на тип double, затем запросить память, которая выделяется во время выполнения. Мы можем использовать следующую инструкцию:new-операторы, чтобы сделать это:

double* pvalue = NULL; // указатель инициализирован значением null pvalue = new double; // запросить память для переменной

Если свободное хранилище исчерпано, выделение памяти может не удаться. Поэтому рекомендуется проверить, возвращает ли оператор new NULL-указатель, и предпринять соответствующие действия следующим образом:

double* pvalue = NULL; if( !(pvalue = new double )) { cout << "Ошибка: недостаточно памяти." <<endl; exit(1); }

malloc()Функция malloc() появилась в языке C и до сих пор существует в C++, но рекомендуется по возможности избегать использования malloc(). По сравнению с malloc(), главное преимущество new в том, что new не только выделяет память, но и создаёт объекты.

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

delete pvalue;        // 释放 pvalue 所指向的内存

В следующем примере используется вышеописанная концепция и демонстрируется, как использовать операторы new и delete:

Пример

#include <iostream> using namespace std; int main () { double* pvalue = NULL; // указатель инициализирован значением null pvalue = new double; // выделение памяти для переменной *pvalue = 29494.99; // сохранить значение по выделенному адресу cout << "Значение pvalue : " << *pvalue << endl; delete pvalue; // освободить память return 0; }

Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:

Value of pvalue : 29495

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

Предположим, мы хотим выделить память для массива символов (строки из 20 символов). Мы можем использовать синтаксис из примера выше, чтобы динамически выделить память для массива, как показано ниже:

char* pvalue  = NULL;   // 初始化为 null 的指针
pvalue  = new char[20]; // 为变量请求内存

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

delete [] pvalue;        // 删除 pvalue 所指向的数组

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

Одномерный массив

// динамическое выделение памяти, длина массива — m int *array=new int [m]; //освобождение памяти delete [] array;

Двумерный массив

int **array; // предположим, что первое измерение массива имеет длину m, а второе измерение — длину n // динамически выделить память array = new int *[m]; for( int i=0; i<m; i++ ) { array[i] = new int [n]; } //освобождение for( int i=0; i<m; i++ ) { delete [] array[i]; } delete [] array;

Тест экземпляра 2D-массива:

Пример

#include <iostream> using namespace std; int main() { int **p; int i,j; //p[4][8] //начать выделение памяти для двумерных данных с 4 строками и 8 столбцами p = new int *[4]; for(i=0;i<4;i++){ p[i]=new int [8]; } for(i=0; i<4; i++){ for(j=0; j<8; j++){ p[i][j] = j*i; } } //вывести данные for(i=0; i<4; i++){ for(j=0; j<8; j++) { if(j==0) cout<<endl; cout<<p[i][j]<<"\t"; } } //начать освобождение выделенной кучи for(i=0; i<4; i++){ delete [] p[i]; } delete [] p; return 0; }

Трёхмерный массив

int ***array; // предположим, что первое измерение массива — m, второе — n, а третье — h // динамически выделить память array = new int **[m]; for( int i=0; i<m; i++ ) { array[i] = new int *[n]; for( int j=0; j<n; j++ ) { array[i][j] = new int [h]; } } //освобождение for( int i=0; i<m; i++ ) { for( int j=0; j<n; j++ ) { delete[] array[i][j]; } delete[] array[i]; } delete[] array;

Тестовый экземпляр 3D-массива:

Пример

#include <iostream> using namespace std; int main() { int i,j,k; // p[2][3][4] int ***p; p = new int **[2]; for(i=0; i<2; i++) { p[i]=new int *[3]; for(j=0; j<3; j++) p[i][j]=new int[4]; } //вывод трехмерных данных p[i][j][k] for(i=0; i<2; i++) { for(j=0; j<3; j++) { for(k=0;k<4;k++) { p[i][j][k]=i+j+k; cout<<p[i][j][k]<<" "; } cout<<endl; } cout<<endl; } // освобождение памяти for(i=0; i<2; i++) { for(j=0; j<3; j++) { delete [] p[i][j]; } } for(i=0; i<2; i++) { delete [] p[i]; } delete [] p; return 0; }

Динамическое выделение памяти для объектов

Объекты ничем не отличаются от простых типов данных. Например, посмотрите на следующий код; мы будем использовать массив объектов, чтобы пояснить эту концепцию:

Пример

#include <iostream> using namespace std; class Box { public: Box() { cout << "Вызов конструктора!" <<endl; } ~Box() { cout << "Вызов деструктора!" <<endl; } }; int main( ) { Box* myBoxArray = new Box[4]; delete [] myBoxArray; // удалить массив return 0; }

Если вы хотите выделить память для массива, содержащего четыре объекта Box, конструктор будет вызван 4 раза; аналогично, когда эти объекты удаляются, деструктор также будет вызван столько же раз (4 раза).

Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:

Calling constructor!
Calling constructor!
Calling constructor!
Calling constructor!
Calling destructor!
Calling destructor!
Calling destructor!
Calling destructor!
Другие расширения