Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ООП.doc
Скачиваний:
23
Добавлен:
08.11.2018
Размер:
1.4 Mб
Скачать
  1. Создание и уничтожение объектов

Рассмотрим, когда и каким образом объекты создаются, то есть когда под них распределяется память и происходит начальное присваивание. Когда мы имеем дело с обычными переменными, возможны два варианта: статические и динамические переменные. Если переменная статическая, то память под нее распределяется автоматически перед началом выполнения соответствующего блока кода – при запуске программы для глобальных переменных и при вызове процедуры для локальных. Если переменная динамическая, то память под нее должна быть распределена вручную вызовом оператора new. Переменные типа класс в Object Pascal описываются как статические:

var AMyObject: TMyObject;

но именно в этом языке они могут быть только динамическими. Это означает, что переменная AMyObject является указателем, содержащим адрес объекта, хотя обращение к полям и методам производится без использования символа (^), как было бы правильно для указателей на структуру. Из этого следуют два вывода:

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

  1. Память под объект должна быть распределена и освобождена явно.

В других объектно-ориентированных языках, например C++, это не так. В них могут быть как статические, так и динамические объекты, хотя используются чаще динамические. Статические объекты создаются автоматически, динамические – вручную.

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

Конструкторы

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

constructor Create; constructor Create(AOwner: TComponent);

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

Класс может иметь более чем один конструктор, но может иметь только один. Обычно имя конструктора Create.

Конструктор всегда должен иметь область видимости public.

Для создания объекта конструктор вызывается через имя класса. Именно класса, а не объекта (т.е. типа, а не переменной) так как объект еще не создан. Пример создания объекта:

var MyObject: TMyObject; ... MyObject := TMyClass.Create;

При этом происходят следующие действия:

  1. Распределяется динамическая память под поля объекта;

  2. Значения всех скалярных полей устанавливаются в 0, всем указателям и полям типа класс присваивается nil, всем полям типа строка присваивается значение «пустая строка»;

  3. Далее выполняются действия, описанные в конструкторе. Обычно это инициализация полей объекта. При этом имеет смысл инициализировать только ненулевые и непустые поля, так как остальные будут проинициализированы автоматически.

  4. По завершению конструктор возвращает указатель на только что распределенный и проинициализированный объект. Тип возвращаемого значения соответствует типу класса, для которого был вызван конструктор.

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

MyObject.Create;

Будьте внимательны! Создания объекта при этом не происходит. Для создания объекта следует вызывать конструктор через имя класса. При попытке вызвать конструктор через имя еще не созданного объекта произойдет ошибка доступа к памяти – конструктор попытается присвоить значения полям, память под которые еще не распределена.

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

constructor Create; overload; constructor Create(pA,pB,pC: integer); overload;

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

  • конструктор по умолчанию обычно не имеет параметров и присваивает полям объекта значения по умолчанию.

  • конструктор инициализации получает в качестве параметров значения, которые присваиваются полям объекта.

  • конструктор копирования получает в качестве параметра указатель на объект того же типа и присваивает своим полям те же значения, что и у полученного объекта. То есть состояние объекта как бы копируется. При этом возникает одна проблема – что делать с указателями. Возможны два варианта: первый – присвоить указателям создаваемого объекта значения указателей копируемого, при этом два объекта будут работать с одной областью памяти; второй – создать копию всех динамических структур и переписать все значения. Нужный вариант выбирается программистом в зависимости от преследуемых им целей. Например, если назначение объектов – обработка одного большого изображения, хранящегося в памяти, то копирование его для каждого из объектов не только трудоемко, но и логически неверно.

Пример использования конструкторов:

interface Type

TMyObject = class private ...

public Property A: integer read GetA write SetA; Property B: integer read GetB write SetB; Property C: real read GetC write SetC;

//конструктор по умолчанию constructor Create; overload;

//конструктор инициализации constructor Create(pA,pB,pC: integer); overload;

//конструктор копирования constructor Create(CopyMyObject: TMyObject); overload; ... end;

Var A1MyObject: TMyObject; A2MyObject: TMyObject; A3MyObject: TMyObject;

implementation

//конструктор по умолчанию constructor TMyObject.Create; begin //Свойствам A и B присваиваются значения по умолчанию A := 1; B := 1; //C остается равным 0 end;

//конструктор инициализации constructor TMyObject.Create(pA,pB,pC: integer); begin //инициализирует свойства полученными значениями A := pA; B := pB; C := pC; end;

//конструктор копирования constructor TMyObject.Create(CopyMyObject: TMyObject); begin //копирует свойства полученного объекта A := CopyMyObject.A; B := CopyMyObject.B; C := CopyMyObject.C; end;

Procedure Test; Begin //Вызываем конструктор по умолчанию A1MyObject := TMyObject.Create; //Вызываем конструктор инициализации A2MyObject := TMyObject.Create(10,20,0.5); ... A1MyObject.B := 1000; //Вызываем конструктор копирования A3MyObject := TMyObject.Create(A1MyObject); ... End;

В этом примере при создании объекта A1MyObject его свойства получат значения (1, 1, 0) – значения по умолчанию, объекта A2MyObject – (10, 20, 0.5) – значения, переданные в конструктор, объекта A3MyObject – (1, 1000, 0) – значения свойств объекта, указатель на который передан в конструктор.

Возможно, что в описании класса отсутствует конструктор. В этом случае компилятором автоматически создается конструктор по умолчанию с именем Create, без параметров, Этот конструктор не выполняет при создании объекта никаких дополнительных действий, за исключением вызова конструктора предка. (Понятие конструктора предка будет рассмотрено позднее). То есть в этом случае все поля остаются равными 0 или nil.

interface type

TMyObject = class; private A: integer; C: real; public ... //Конструктор не объявлен end;

...

var AMyObject: TMyObject;

implementation

Procedure Test; Begin AmyObject := TMyObject.Create; //Будет автоматически сгенерирован и вызван конструктор //по умолчанию. Поля останутся равными 0 ... End;

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

interface type

TMyObject = class; private A: integer; C: real; public //Объявим конструктор инициализации constructor Create(ValueA: integer; ValueB: real); ... end;

...

var AMyObject:TMyObject;

implementation

Procedure Test; Begin //AMyObject := TMyObject.Create; //Вызовет ошибку, //поскольку у класса нет конструктора без параметров ... End;

Приведем пример. Дополним описание класса стек из прошлого примера конструкторами. Пусть это будут конструктор по умолчанию и конструктор инициализации, помещающий в стек первое значение.

1. Стек в массиве

TSObStack = class private Stack:array[1..1000] of TElem; Top:integer; public Constructor Create; overload; //конструктор по умолчанию Constructor Create(e: TElem); overload; //конструктор инициализации ... procedure InStack(Elem: TElem); function OutStack(var Elem: TElem): boolean; ... end;

Constructor TSObStack.Create; Begin Top := 0; //можно было не делать этого //конструктор можно было бы не определять – достаточно было бы //конструктора по умолчанию, генерируемого компилятором //но он необходим поскольку есть еще конструктор инициализации End;

Constructor TSObStack.Create(e: TElem); Begin Top := 0; //можно было не делать этого InStack(TElem);//добавим первый элемент в стек End;

Описание объекта по-прежнему не является полным, отсутствуют элемент, отвечающий за уничтожение объекта. Он будет рассмотрен позднее.

2. Динамический стек

TDObStack = class private Top:TERef; public Constructor Create; overload; //конструктор по умолчанию Constructor Create(e: TElem); overload; //конструктор инициализации ... procedure InStack(Elem: TElem); function OutStack(var Elem: TElem): boolean; ... end;

Constructor TDObStack.Create; Begin Top := nil; //можно было не делать этого //конструктор можно было бы не определять – достаточно было бы //конструктора по умолчанию, генерируемого компилятором //но он необходим поскольку есть еще конструктор инициализации End;

Constructor TDObStack.Create(e: TElem); Begin Top := nil; //можно было не делать этого InStack(TElem);//добавим первый элемент в стек End;

End;

Описание объекта также не является полным.

Перед использованием объектов следует вызвать их конструкторы:

Procedure Test; var DStack: TDObStack; SStack: TSObStack; a: TElem; begin //создание объектов DStack := TDObStack.Create; //создаем пустой стек SStack := TSObStack.Create(10);//создаем стек //и помещаем в него значение 10 ... //использование объектов и их уничтожение

end.