Практический курс по C/C++: https://stepik.org/course/193691
Итак, на
предыдущих занятиях мы с вами увидели, каким богатым функционалом стали
обладать структуры в языке С++. В них появились методы-члены, конструкторы и
деструкторы, а также режимы ограничения доступа к полям структуры. В связи с
этим возникают некоторые особенности их динамического создания в памяти
устройства.
Я напомню, что в
языке Си мы изучали функции malloc(), calloc(), realloc() и free() для
динамического выделения и освобождения ранее выделенной области памяти. В
частности, память под структуру могли выделить следующим образом:
#include <iostream>
#include <math.h>
using std::cout;
using std::endl;
struct point {
private:
int x, y;
public:
point()
{
cout << "вызов конструктора объекта" << endl;
x = 0; y = 0;
}
point(int x, int y)
{
cout << "вызов конструктора объекта" << endl;
this->x = x; this->y = y;
}
~point() // деструктор
{
cout << "вызов деструктора объекта" << endl;
}
double length() { return sqrt(x*x + y*y); }
void sum(const point& pt)
{
this->x += pt.x;
this->y += pt.y;
}
void set_coords(int x, int y)
{
if(x < -100 || x > 100 || y < -100 || y > 100)
return;
this->x = x;
this->y = y;
}
void get_coords(int& x, int& y) {x = this->x; y = this->y; }
int get_x() { return this->x; }
int get_y() { return this->y; }
};
int main()
{
struct point* pt;
pt = (point *)malloc(sizeof(point));
// что то делаем
free(pt);
return 0;
}
Однако если
запустить программу, то ни конструктор, ни деструктор структуры point вызван не будет.
Функции malloc() и free() лишь выделяют
нужное число байт для хранения объекта структуры и, затем, освобождают эту
память. Никаких дополнительных действий не выполняется. В ряде случаев – это
то, что нужно. И в языке Си этого достаточно для размещения объекта структуры в
памяти. Но не в С++. Здесь дополнительно следует вызвать конструктор
непосредственно после создания объекта и деструктор – непосредственно перед
удалением объекта. Для этого Бьёрн Страуструп ввел в С++ два новых оператора:
- new – для выделения
памяти под указанный тип данных с автоматическим вызовом конструктора;
- delete – освобождение
памяти с автоматическим вызовом деструктора.
В нашем примере
ими можно воспользоваться следующим образом:
int main()
{
struct point* pt;
pt = new point;
// что то делаем
delete pt;
return 0;
}
После выполнения
программы в консоли увидим строчки:
вызов
конструктора объекта
вызов
деструктора объекта
Они появились
благодаря тому, что компилятор, при использовании оператора new, добавил в
машинный код вызов функции-конструктора сразу после создания объекта, и вызов
функции-деструктора перед его удалением. Также обратите внимание, что нам не
требуется прописывать приведение типов у оператора new, так как он
возвращает указатель на тип данных, для которого выполняется выделение памяти.
Вообще, в С++ рекомендуется
(по возможности) использовать эти новые операторы new и delete вместо прежних malloc() и free(). Мало того,
крайне не рекомендуется их смешивать. Например, выделить память с помощью new, а освободить с
помощью free(). Это плохая
практика, которая может привести к непредсказуемым результатам работы
программы.
Но вернемся к
нашей программе. При выполнении команды:
вызывается
конструктор без параметров (конструктор по умолчанию). И если вывести
координаты x
и y в консоль:
cout << pt->get_x() << " " << pt->get_y() << endl;
то увидим нули. А
что если в момент создания нового объекта мы хотим сразу передать ему некоторые
координаты? Для этого после типа point ставятся круглые
скобки и прописываются параметры, например, так:
В этом случае
будет вызван другой конструктор с двумя параметрами. Кстати, если указать
только один аргумент при создании объекта:
pt = new point(10); // ошибка
то получим
ошибку при компиляции программы, т.к. в структуре point не объявлен
конструктор с одним параметром.
Интересно, что
операторы new и delete в С++ можно
использовать и с базовыми типами. Например, так:
int *ptr_int = new int;
delete ptr_int;
Будет создан, а
затем, удален целочисленный объект int в памяти устройства с
неопределенным значением. Но у этого объекта есть конструктор, через который
можно инициализировать переменную int следующим образом:
int *ptr_int = new int(-123);
Тогда начальное
значение будет равно -123. И так можно делать с любыми другими базовыми типами
языка С++.
Операторы new[] и delete[]
Давайте теперь
посмотрим, как можно динамически создавать массивы из объектов определенного
типа данных. Для этого в С++ применяются похожие операторы:
- new [] – для
выделения памяти под указанное число объектов;
- delete [] – для
освобождения памяти массива объектов.
Например:
int main()
{
point* pt = new point[3];
// что то делаем
delete [] pt;
return 0;
}
После запуска
программы в консоли увидим:
вызов
конструктора объекта
вызов
конструктора объекта
вызов
конструктора объекта
вызов
деструктора объекта
вызов
деструктора объекта
вызов
деструктора объекта
То есть, было
выделена непрерывная область памяти под три объекта типа point и для каждого
объекта был вызван конструктор по умолчанию (без параметров). При удалении с
помощью оператора delete [], также для каждого объекта был
вызван деструктор. Именно в этом ключевое отличие операторов new [] и delete [] от
операторов new/delete. Например, если
последний оператор delete[] заменить на delete:
то будет вызван
только один деструктор для первого объекта массива. Для остальных элементов
деструкторы уже не вызываются. Как вы понимаете, это может привести к утечке
памяти, так как в деструкторах, обычно, происходит освобождение ресурсов,
захваченных текущим объектом (структурой). Поэтому, при выделении памяти с помощью
оператора new[] обязательно
нужно ее освобождать оператором delete[] и комбинировать их вызовы с
другими операторами недопустимо.
Далее, с
массивом pt мы можем
работать абсолютно так же, как с любым динамическим массивом. Например,
перебрать и вывести координаты объектов:
for(int i = 0; i < 3; ++i)
cout << pt[i].get_x() << " " << pt[i].get_y() << endl;
Обратите
внимание, что использовать цикл:
for(const point& p : pt) …
не получится,
так как pt – это обычный
указатель на первый элемент массива и сам по себе не считается массивом языка
Си или С++.
Те же самые
операции мы можем выполнять и с любым другим типом данных:
double* vector3 = new double[3];
delete [] vector3;
Вот так работают
операторы new/delete и new[]/delete[] в языке С++.
Практический курс по C/C++: https://stepik.org/course/193691