Назад | Начало урока | Вперед
Содержание

Глава 8

Создание объектов в области динамической памяти

В динамической памяти можно размещать объекты любых классов. Например
если объявить класс Cat , то для манипулирования этим объектом можно
создать указатель, в котором будет храниться его адрес.

Cat*pCat = new Cat;

В данном случае в операторе new использован конструктор класса
по умолчанию, то есть без параметров. Следует помнить что при создании
объекта класса конструктор вызывается всегда, не зависимо от того
размещается объект в стеке или в области динамической памяти.

Вверх

Удаление объектов

Создание объекта в области динамической памяти использовав
оператор new и удаление объекта

// Listing 8.5
// Creating objects on the free store
// using new and delete


#include <iostream.h>

class SimpleCat{

public:

SimpleCat();
~SimpleCat();
private:
int itsAge;
};

SimpleCat::SimpleCat(){

cout << "Constructor called.\n";
itsAge = 1; }

SimpleCat::~SimpleCat(){

cout << "Destructor called.\n"; }

int main(){

cout << "SimpleCat Frisky...\n";
SimpleCat Frisky;

cout << "SimpleCat *pRags = new SimpleCat...\n";
SimpleCat * pRags = new SimpleCat;

cout << "delete pRags...\n";
delete pRags;

cout << "Exiting, watch Frisky go...\n";
return 0;

}


Результат:

1.SimpleCat Frisky...
2.Constructor called.
3.SimpleCat *pRags = new SimpleCat...
4.Constructor called.
5.delete pRags...
6.Destructor called.
7.Exiting, watch Frisky go...
8.Destructor called.

Краткое содержание программы:

Сначала создается объект Frisky типа SimpleCat в области стека.
Затем при помощи оператора new создается такой же объект правда
без имени в динамической памяти и на него указывает указатель pRags .
Затем этот объект удаляется из динамической памяти а при выходе из
программы удаляется и объект из стека.

Мы видим класс в котором прототипы функций конструктора и деструктора
а также одна закрытая переменная обозначающая возраст кота.
Посмотрим главную программу:

  1. Сначала в стеке создается объект Frisry класса SimpleCat

    cout << "SimpleCat Frisky...\n";
    SimpleCat Frisky;

  2. При этом используется стандартный конструктор без параметров.

  3. Далее в динамической памяти создается экземпляр класса SimpleCat
    на него указывает указатель pRags. Как мы видим указатель указывает
    на созданный в динамической памяти экземпляр класса SimpleCat
    но у самого этого экземпляра нет имени. Как было имя при создании
    экземпляра в стеке (Frisky). Очевидно в имени здесь нет необходимости.
    (можно имя экземпляра созданного в динамической памяти определить так:
    "экземпляр класса SimpleCat созданный в динамической памяти на который
    указывает указатель pRags" Вот такое длинное имя)

    cout << "SimpleCat *pRags = new SimpleCat...\n";
    SimpleCat * pRags = new SimpleCat;

  4. Конструктор для данного экземпляра так же стандартный.

  5. В следующей строке указатель pRags освобождается с помощью оператора
    delete, (освобождается и место в динамической памяти)
    при этом вызывается деструктор класса.

    cout << "delete pRags...\n";
    delete pRags;

    Теперь можно было бы инициализировать указатель новым местом в динамической
    памяти например так(или новым объектом):

    pRags = new SimpleCat;

    Проверь это на практике!

  6. Далее сообщение из main()

    cout << "Exiting, watch Frisky go...\n";

  7. Деструктор объекта Frisky вызывается по умолчанию, когда он выходит
    из области действия по завершению функции main() и освобождается стек.

Вверх

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

Для доступа к переменным-членам и функциям-членам, созданным в динамической
памяти, необходимо использовать ссылку на указатель и точечный оператор.
(*pRags).GetAge();Круглые скобки используются для того чтобы сначала
обратиться к значению по адресу в указателе, а только затем к его
функции GetAge().
или лучше использовать оператор косвенного доступа "указатель на" [->].
что то же самое но более популярно.

// Listing 8.6
// Accessing data members of objects on the heap
// using the -> operator


#include <iostream>

class SimpleCat{

public:

SimpleCat() {itsAge = 2;}
~SimpleCat()
int GetAge() const {return itsAge;}
void SetAge(int age) {itsAge = age;}
private:
int itsAge;
};

int main(){

SimpleCat * Frisky = new SimpleCat;

cout << "Frisky is " << Frisky->GetAge() << " years old\n";
Frisky->SetAge(5);
cout << "Frisky is " << Frisky->GetAge() << " years old\n";
delete Frisky;
return 0;

}


Результат:

Frisky is 2 years old
Frisky is 5 years old

Здесь представлен тот же класс SimpleCat в нем добавлена функция
GetAge() позволяющая увидеть закрытую переменную itsAge и
функция SetAge() позволяющая извне изменить значение закрытой переменной itsAge.

Посмотрим главную программу:

Сначала в динамической памяти создается объект -экземпляр класса SimpleCat.
Заданный по умолчанию конструктор устанавливает его возраст равным 2.

SimpleCat * Frisky = new SimpleCat;

Далее вызывается функция GetAge() которая позволяет удостовериться что
конструктор сработал правильно.

cout << "Frisky is " << Frisky->GetAge() << " years old\n";

Поскольку речь идет об указателе то для обращения к данным-членам
и функциям-членам объекта на который он указывает,
используется оператор косвенного доступа ( -> )

Далее при помощи функции SetAge() устанавливается новое значение
возраста опять же используется оператор косвенного доступа и указатель.

Frisky->SetAge(5);

Затем вновь вызывается функция позволяющая посмотреть как изменился
возраст кота.

cout << "Frisky is " << Frisky->GetAge() << " years old\n"; Далее освобождается динамическая память и указатель на нее.
delete Frisky;

Вверх

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

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

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

Посмотрите пример:
Указатели как члены класса.
// Listing 8.7
// Pointers as data members
// accessed with -> operator


#include <iostream>

class SimpleCat {

public:

SimpleCat();
~SimpleCat();
int GetAge() const {return *itsAge;}
void SetAge(int age) {*itsAge = age;}

int GetWeight() const {return *itsWeight;}
void setWeight (int weight) {*itsWeight = weight;}

private:
int * itsAge;
int * itsWeight;

}

SimpleCat::SimpleCat(){

itsAge = new int(2);
itsWeight = new int(5);
}

SimpleCat::~SimpleCat(){

delete itsAge;
delete itsWeight;
}

int main(){

SimpleCat *Frisky = new SimpleCat;
std::cout << "Frisky is " << Frisky->GetAge() << " years old\n";

Frisky->SetAge(5);

std::cout << "Frisky is " << Frisky->GetAge() << " years old\n";
delete Frisky;

return 0;

}


Результат:
Frisky is 2 years old
Frisky is 5 years old

Анализ:

В составе объявленного класса SimpleCat находятся две переменные-члена,
являющиеся указателями на тип int

private:

int * itsAge;
int * itsWeight;

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

SimpleCat::SimpleCat(){

itsAge = new int(2);
itsWeight = new int(5);
}

Обратите внимание на псевдоконструктор который не только выделяет
в динамической памяти место для переменных, но и инициализирует их
начальными значениями переменную itsAge -значением 2, переменную
itsWeight-значением 5.

Деструктор освобождает выделенную память

SimpleCat::~SimpleCat(){

delete itsAge;
delete itsWeight;
}

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

Посмотрим главную программу.
Вызывающей функции main() абсолютно безразлично что переменные itsAge
itsWeight являются указателями на области в динамической памяти.
Функция main() просто вызывает функции GetAge() и SetAge(),
а подробности управления памятью инкапсулированы в реализации класса,
как и должно быть.

SimpleCat *Frisky = new SimpleCat;
std::cout << "Frisky is " << Frisky->GetAge() << " years old\n";

Frisky->SetAge(5);

std::cout << "Frisky is " << Frisky->GetAge() << " years old\n";
delete Frisky;

При уничтожении класса Frisky вызывается деструктор класса SimpleCat.
Деструктор удаляет каждый из указателей принадлежащих объекту.
Если они в свою очередь указывают на объекты других классов,
то будут вызваны так же и их деструкторы.

В реальной программе это выглядело бы довольно глупо обращаться
к своим переменным по ссылкам, если только у объекта нет достаточно
серьезных оснований для этого.
Какие реальные основания у объекта обращаться к своим переменным по ссылкам?

Необходимо четко ставить задачу , которую предстоит решить.

Допустим необходимо создать класс, членом которого будет объект другого
класса, причем второй объект может создаваться еще до возникновения
первого и существовать после его удаления. В этом случае доступ ко
второму объекту должен осуществляться только по ссылке, то есть
с помощью указателя.
Допустим первым объектом является окно, а вторым-документ. Вполне понятно
что окно должно иметь доступ к документу. С другой стороны
продолжительность существования документа никак не контролируется окном.
Поэтому для окна важно хранить лишь ссылку на этот документ.
Об использовании ссылок в уроке 9.


Вверх

Часто задаваемые вопросы:

Если в стеке объявлен объект, который имеет переменные -члены, размещенные
в динамической памяти, что будет находиться в стеке, а что в динамической памяти?

Пример:

#include <iostream>

class SimpleCat{

public:

SimpleCat();
~SimpleCat();
int GetAge() const {return *itsAge;}
//другие функции

private:

int * itsAge;
int * itsWeight;

}

SimpleCat::SimpleCat(){

itsAge = new int(2);
itsWeight = new int(5);
}

SimpleCat::~SimpleCat(){

delete itsAge;
delete itsWeight;
}

int main() {

SimpleCat Frisky;
cout << "Frisky is " << Frisky.GetAge() << " years old\n";

Frisky.SetAge(5);
cout << "Frisky is " << Frisky.GetAge() << " years old\n";

return 0; }

В стеке находится локальный объект Frisky. Этот объект обладает двумя
указателями, каждый из которых занимает по четыре байта пространства
стека, (это как обычно)и содержит адрес целого числа, находящегося в
динамической памяти. Таким образом восемь байт находятся в стеке, и восемь
байт в динамической памяти.


Запомните если используется стрелочка -> то значит что объект расположен
в динамичкеской памяти.
cout << "Frisky is " << Frisky->GetAge() << " years old\n";

Вверх

Указатель this

Каждая функция класса имеет скрытый указатель this.
Этот указатель содержит адрес текущего объекта. Следовательно при каждом
обращении к функциям GetAge() и SetAge() указатель объекта this
включается как скрытый параметр.
Пример явного использования указателя this приведен в листинге 8.8

// Listing 8.8
// Using the this pointer


#include <iostream.h>

class Rectangle{

public:

Rectangle();
~Rectangle();

void SetLength(int length)
{ this->itsLength = length;}

int GetLength() const
{ return this->itsLength;}

void SetWidth(int width)
{ itsWidth = width;}

int GetWidth() const
{ return itsWidth;}

private:

int itsLength;
int itsWidth;

};

Rectangle::Rectangle(){

itsWidth = 5;
itsLength = 10;
}

Rectangle::~Rectangle()

int main(){

Rectangle theRect;
cout << "theRect is " << theRect.GetLength()
<< " feet long.\n";
cout << "theRect is " << theRect.GetWidth()
<< " feet wide.\n";

theRect.SetLength(20);
theRect.SetWidth(10);

cout << "theRect is " << theRect.GetLength()
<< " feet long.\n";
cout << "theRect is " << theRect.GetWidth()
<< " feet wide.\n";
return 0;

}

Функции доступа SetLenth() и GetLenth() используют указатель this
для обеспечения доступа к переменным-членам объекта Rectangle
в явном виде, а функции доступа SetWeigth() и GetWeigth()
организованы традиционным способом. Как видите они ведут себя
одинаково, хотя синтаксис последних проще.

Роль указателя this важнее чем это может показаться. Поскольку
this является указателем, он содержит адрес текущего объекта
и в этой роли может оказаться достаточно мощным инструментом.

Необходимо усвоить что каждый объект обладает скрытым указателем
this содержащим адрес самого объекта.(подробнее глава 10)

О создании и удалении указателя this позаботится сам компилятор.


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz