Чтобы не перегружать примеры излишней информацией, мы опускаем всякого рода проверки. Однако во избежание аварийного завершения программы при решении реальных задач такой контроль обязательно должен выполняться.
В строке 10, после выделения памяти, по адресу в указателе записывается значение 7. Затем в строках 11 и 12 значения локальной переменной и указателя pLocal выводятся на экран. Вполне понятно, почему эти значения равны. Далее, в строке 13, выводится значение, записанное по адресу, хранящемуся в указателе рНеар. Таким образом, подтверждается, что значение, присвоенное в строке 10, действительно доступно для использования.
Освобождение области динамической памяти, выделенной в строке 9, осуществляется оператором delete в строке 14. Освобожденная память становится доступной для дальнейшего использования, и ее связь с указателем разрывается. После этого указатель рНеар может использоваться для хранения нового адреса. В строках 15 и 16 выполняется повторное выделение памяти и запись значения по соответствующему адресу. Затем в строке 17 это значение выводится на экран, после чего память освобождается.
Вообще говоря, строка 18 не является обязательной, так как после завершения работы программы вся выделенная в ней память автоматически освобождается. Однако явное освобождение памяти считается как бы правилом хорошего тона в программировании. Кроме того, это может оказаться полезным при редактировании программы.
Что такое утечка памяти
При невнимательной работе с указателями может возникнуть эффект так называемой утечки памяти. Это происходит, если указателю присваивается новое значение, а память, на которую он ссылался, не освобождается. Ниже показан пример такой ситуации.
1: unsigned short int * pPointer = new unsigned short int;
2: *pPointer = 72;
3: delete pPointer;
4: pPointer = new unsigned short int;
5: *pPointer = 84;
В строке 1 объявляется указатель и выделяется память для хранения переменной типа unsigned short int. В следующей строке в выделенную область записывается значение 72. Затем в строке 3 указателю присваивается адрес другой области памяти, в которую записывается число 84 (строка 4). После выполнения таких операций память, содержащая значение 72, оказывается недоступной, поскольку указателю на эту область было присвоено новое значение. В результате невозможно ни использовать, ни освободить зарезервированную память до завершения программы. Правильнее было бы написать следующее:
1: unsigned short int * pPointer = new unsigned short int;
2: *pPointer = 72;
3: pPointer = new unsigned short int;
4: *pPointer = 84;
В этом случае память, выделенная под переменную, корректно освобождается (строка 3).
Примечание:
Размещение объектов в области динамической памяти
Аналогично созданию указателя на целочисленный тип, можно динамически размещать в памяти любые объекты. Например, если вы объявили объект класса Cat, для манипулирования этим объектом можно создать указатель, в котором будет храниться его адрес, — ситуация, абсолютно аналогичная размещению переменных в стеке. Синтаксис этой операции такой же, как и для целочисленных типов:
Cat *pCat = new Cat;
В данном случае использование оператора new вызывает конструктор класса по умолчанию, т.е. конструктор, использующийся без параметров. Важно помнить, что при создании объекта класса конструктор вызывается всегда, независимо от того, размещается объект в стеке или в области динамического обмена.
Удаление объектов
При использовании оператора delete, за которым следует идентификатор указателя на объект, вызывается деструктор соответствующего класса. Это происходит еще до освобождения памяти и возвращения ее в область динамического обмена. В деструкторе, как правило, освобождается вся память, занимаемая объектом класса. Пример динамического размещения и удаления объектов показан в листинге 8.5.
Листинг 8.5. Размещение и удаление объектов в области динамического обмена
1: // Листинг 8.5.
2: // Размещение и удаление объектов в области динамического обмена
3:
4: #include
5:
6: class SimpleCat
7: {
8: public:
9: SimpleCat;
10: ~SimpleCat;
11: private:
12: int itsAge;
13: };
14:
15: SimpleCat::SimpleCat
16: {
17: cout << "Constructor called.\n";
18: itsAge = 1;
19: }
20:
21: SimpleCat::~SimpleCat
22: {
23: cout << "Destructor called.\n";
24: }
25:
26: int main
27: {
28: cout << "SimpleCat Frisky...\n";
29: SimpleCat Frisky;
30: cout << "SimpleCat *pRags = new SimpleCat...\n";