![](/user_photo/1319_hJRrZ.png)
[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi
.pdf281
16.4. Конструкторы и деструкторы
Для того, чтобы создавать объекты, в каждом классе есть специальный метод, который называется конструктором (когда мы говорили о чудо-записях, то мы называли такую подпрограмму настройщицей). Любой конструктор выделяет в куче память под объект и возвращает ссылку на этот объект. Кроме этого, внутри конструктора можно выполнять и другие действия, например, заполнять поля объекта базовыми значениями.
Заметьте: создается объект в динамической памяти, а возвращается ссылка на объект. Благодаря этому вы можете работать с переменными, расположенными в динамической памяти без непосредственного использования указателей.
Объявляется конструктор следующим образом: constructor Name(<параметры>);
<параметры> - некоторый набор параметров. constructor – зарезервированное слово. Name – имя конструктора.
Тип выходного параметра не описывается, т.к. и без того известно, что это ссылка на экземпляр класса.
Для того чтобы уничтожать объект из динамической памяти, у класса есть методы, называемые деструкторами.
Описывается деструктор так:
destructor DestrName(<параметры>);
Даже если вы объявите пустой класс:
type
Klasse = class end;
то все равно объекты этого класса будут по умолчанию иметь стандартный набор методов. Они появляются в любом классе т.к. в Delphi существует стандартный класс TObject, у которого есть набор методов, которые должны быть, по мнению разработчиков Delphi, у каждого класса. А переносятся все эти свойства из этого класса
вдругие при помощи специального механизма, называемого наследованием, который мы изучим немного позже.
Пока для нас важно, что среди стандартных методов есть уже базовый конструктор Create и базовый деструктор Destroy. Create просто выделяет в куче память под объект и возвращает ссылку на него (никакой начальной инициализации нет). Destroy очищает память, занимаемую непосредственно объектом и делает ссылку равной nil. Если же объект содержит ссылки на другие объекты, которые находятся в динамической памяти, то при использовании базового деструктора Destroy эти данные будут безвозвратно утеряны. Но если вам не подходит стандартный деструктор, вы можете написать собственный.
Теперь мы напишем простейшую программу с использованием классов. В ней мы опишем простой класс Klasse, у которого есть только одно поле целого типа. Мы хотим, чтобы переменные типа Klasse не могли быть отрицательными. Следовательно,
впроцедуре, в которой устанавливается значение поля (StellZahl) если входной параметр отрицателен, то значение поля х устанавливается равным 0.
282
Пример 1: Пишем простейший класс.
/////////// модуль KLasseU.pas ////////////
1 : unit KlasseU;
2 :
3 : interface
4 :
5 : type
6 : Klasse=class
7 : private
8 : x:integer;
9 : public
10:constructor Bilde(x1:integer); {строит объект}
11:procedure StellZahl(x1:integer); {stellen - ставить}
12:function GibZahl:integer; {GibZahl - "дай число"}
13:end;
14:
15:
16: implementation 17:
18:constructor Klasse.Bilde(x1:integer);{строит объект}
19:begin
20:StellZahl(x1);
21:end;
22:
23:procedure Klasse.StellZahl(x1:integer); {stellen - ставить}
24:begin
25:if x1>=0 then
26:x:=x1
27:else
28:x:=0;
29:end;
30:
31:function Klasse.GibZahl:integer; {GibZahl - "дай число"}
32:begin
33:result:=x;
34:end;
35:
36: end.
Файл проекта:
1 : program Erste;
2 :
3 : {$APPTYPE CONSOLE}
4 :
5 : uses
6 : SysUtils,
7 : KlasseU;
8 :
9 : var
10:X,Y:klasse;
11:begin
12:X:=Klasse.Bilde(-34);
283
13:Y:=X;
14:writeln(Y.GibZahl); {0}
15:Y.StellZahl(56);
16:writeln(Y.GibZahl); {56}
17:writeln(X.GibZahl); {56}
19:readln;
20:end.
Заметьте, что при описании методов класса в части implementation перед именем метода должно ставиться имя класса, которому этот метод принадлежит и точка.
Вы видите, что параметр х объявлен в секции private, значит, он недоступен вне модуля, в котором объявлен класс. Для того чтобы устанавливать значение поля х и получать его, в классе объявлены 2 подпрограммы: StellZahl и GibZahl. В процедуре StellZahl которой сначала проверяется, будет ли число неотрицательным. Если да, то оно и записывается в поле х в качестве нового значения. В противном случае в х записывается 0.
Конструктор размещает объект в динамической памяти и возвращает указатель на объект, даже если он пустой. Однако мы хотим, чтобы при создании объекта можно было бы сразу установить его поле, поэтому и используем процедуру StellZahl.
В основной программе может возникнуть вопрос лишь о смысле 12 строки. Для того чтобы создать объект, вызывается конструктор как метод самого класса, а не объекта. Как писать методы, которые относились бы к классу в целом, мы разберем потом. Пока же запомните, что конструктор надо вызывать именно как метод класса. Таким образом, после выполнения строки 12 в Х будет записана ссылка на объект класса Klasse.
После выполнения оператора присваивания в 13-й строке, Y будет указывать на тот же объект, что и Х.
Операция точка в операторе Y.GibZahl означает следующее: будет разыменована ссылка Y, а затем будет вызвана процедура объекта, на который указывает Y.
16.5. Агрегация классов
Объекты, также как и записи, могут быть полями объекта другого класса. Такой способ конструирования классов называется агрегацией.
В следующем примере мы построим 3 класса, описывающие геометрические фигуры в трехмерном пространстве: точка, круг и шар, используя агрегацию.
Пример 2: Агрегация классов.
|
|
////////////////// Модуль PunktU.pas ////////////////////// |
1 |
: unit PunktU; |
|
2 |
: |
|
3 |
: interface |
|
4 |
: uses |
|
5 |
: |
SysUtils;//Для функции FloatToStr |
6 |
: type |
|
7 |
: |
Punkt = class //der Punkt - точка |
8 |
: |
private |
9 |
: |
x,y,z:real; |
284
10:public
11:constructor Bilde(x1,y1,z1:real); //конструктор точки
12:procedure StellPunkt(x1,y1,z1:real);//устанавливает
координаты точки
13:function ZurZeile:string; //возвращает точку в виде строки
14:end;
15:
16: implementation 17:
18:constructor Punkt.Bilde(x1,y1,z1:real);
19:begin
20://Память уже выделена и указатели на подпрограммы правильно
21:// настроены. Значит, можно устанавливать начальные значения
22:StellPunkt(x1,y1,z1);
23:end;
24:
25:procedure Punkt.StellPunkt(x1,y1,z1:real);
26:begin
27:x:=x1;
28:y:=y1;
29:z:=z1;
30:end;
31:
32:function Punkt.ZurZeile:string;
33:begin
34:
ZurZeile:='('+FloatToStr(x)+';'+FloatToStr(y)+';'+FloatToStr(z)+')'; 35: end;
36:
37: end.
////////////////// Модуль KreisU.pas //////////////////////
1 |
: unit KreisU; |
|
2 |
: |
|
3 |
: interface |
|
4 |
: |
|
5 |
: uses |
|
6 |
: |
PunktU,SysUtils; |
7 |
: type |
|
8 |
: |
Kreis = class //der Kreis - круг |
9 |
: |
private |
10:Zentrum:Punkt;
11:rad:real;
12:public
13:constructor Bilde(x,y,z,r:real);
14:procedure StellKreis(x,y,z,r:real);
15:procedure StellRad(r:real);
16:function ZurZeile:string;
17:function GibRad:real;
18:end;
19:
20:implementation
21:procedure Kreis.StellRad(r:real);
285
22:begin
23:if r>=0 then
24:rad:=r
25:else
26:rad:=0;
27:end;
28:
29:constructor Kreis.Bilde(x,y,z,r:real);
30:begin
31://Память выделена для функций и полей, кроме вложенных объектов
32://В данный момент Zentrum = nil, значит надо создать объект.
33:Zentrum:=Punkt.Bilde(x,y,z);
34://Записать вместо предыдущей строки Zentrum.StellPunkt(x,y,z)
нельзя, т.к.
35: //объект еще не существует в памяти, и функции не работают как надо.
36:StellRad(r);
37:end;
38:
39:procedure Kreis.StellKreis(x,y,z,r:real);
40:begin
41:Zentrum.StellPunkt(x,y,z);
42:StellRad(r);
43:end;
44:
45:function Kreis.ZurZeile:string;
46:begin
47:result:=Zentrum.ZurZeile +' , Rad = '+FloatToStr(rad);
48:end;
49:
50:function Kreis.GibRad:real;
51:begin
52:result:=rad;
53:end;
54:
55: end.
////////////////// Модуль KugelU.pas //////////////////////
1 : unit KugelU;
2 :
3 : interface
4 : uses
5 : KreisU;
6 : type
7 : Kugel = class //die Kugel - шар
8 : private
9 : HauptKreis:Kreis;//сечение шара плоскостью, проходящей через центр шара.
10:public
11:constructor Bilde(x,y,z,r:real);
12:procedure StellKugel(x,y,z,r:real);
13:function Umfang:real; //der Umfang - объем.
286
14:function ZurZeile:string;
15:end;
16:
17:implementation
18:constructor Kugel.Bilde(x,y,z,r:real);
19:begin
20:HauptKreis:=Kreis.Bilde(x,y,z,r);
21:end;
22:
23:procedure Kugel.StellKugel(x,y,z,r:real);
24:begin
25:HauptKreis.StellKreis(x,y,z,r);
26:end;
27:
28:function Kugel.ZurZeile:string;
29:var
30:s:string;
31:begin
32:result:=HauptKreis.ZurZeile;
33:end;
34:
35:function Kugel.Umfang:real; //der Umfang - объем.
36:begin
37:result:=(4/3)*Pi*HauptKreis.GibRad*
HauptKreis.GibRad*HauptKreis.GibRad;
38:end;
39:
40: end.
////////////////// Основной файл проекта //////////////////////
1 : program Agreg;
2 :
3 : {$APPTYPE CONSOLE}
4 :
5 : uses
6 : SysUtils,
7 : PunktU in 'PunktU.pas',
8 : KreisU in 'KreisU.pas',
9 : KugelU in 'KugelU.pas'; 10:
11:var
12:P:Punkt;
13:K:Kugel;
14:begin
15:P:=Punkt.Bilde(1,2,3);//вызываем конструктор для Punkt
16:writeln(P.ZurZeile);
17:K:=Kugel.Bilde(1,2,5,5);//вызываем конструктор для Kugel
18:writeln(K.ZurZeile);
19:readln;
20:end.
287
В программе реализованы 3 класса: Punkt (точка), Kreis (круг), Kugel (шар), и написаны некоторые базовые подпрограммы к ним. Каждый класс реализован в своем модуле. Давайте сначала рассмотрим класс Punkt (файл PunktU.pas).
У этого класса есть 3 закрытых поля – пространственные координаты точки. Кроме того, у класса есть конструктор Bilde (bilden – формировать, создавать) и 2
метода:
•StellPunkt, с помощью которого можно устанавливать координаты точки.
•ZurZeile, который записывает представление точки в виде строки.
Единственный тонкий момент во всех классах - реализация конструкторов. В дополнение к конструктору каждого из классов мы пишем еще один конструктор – Bilde. У класса Punkt есть 3 поля, которым сразу после создания объекта надо присвоить некоторые начальные значения. Для этого они должны передаваться в конструктор в качестве параметров.
Важно знать, что любой конструктор, который вы пишете сами, в начале все равно вызывает встроенный конструктор Create, поэтому даже если вы не напишете ни одной строки в конструкторе, все равно объект будет создан и ссылку на него конструктор вернет. Когда в строке 22 вызывается процедура StellPunkt, объект уже существует в памяти, поэтому его полям можно присваивать начальные значения.
Теперь давайте разберем реализацию класса Kreis. У этого класса 2 поля – радиус типа real и Zentrum – центр круга типа Punkt. В реализации методов нет ничего сложного, а реализация конструктора Bilde требует дополнительных пояснений. Как я уже писал, любой конструктор выделяет память под все поля. Это означает, что в начале реализации конструктора Bilde класса Kreis уже существует объект класса Kreis. По умолчанию все содержимое класса заполняется нулями (т.е. во всех вложенных ссылках будет находится nil). Для того, чтобы в Zentrum записать ссылку на объект класса Punkt, надо вызвать соответствующий конструктор (см. строку 33 файла
Kreis.pas).
Реализацию класса Kugel вы сможете разобрать самостоятельно.
Для того чтобы достичь инкапсуляции, надо писать много функций, которые бы позволяли получать значения некоторых полей, и устанавливать их. Поэтому объем программ несколько увеличится. Но зато программа получается более структурированной и не надо волноваться, что объект может содержать недопустимые значения. При написании больших проектов лучшая структурированность важнее, чем небольшое увеличение объема исходного кода, а хорошие компиляторы позволяют свести потери при большом количестве дополнительных вызовов функций к минимуму, поэтому скорость работы приложений тоже не сильно страдает.
16.6. Наследование
Часто случается, что один класс очень похож на другой, только добавилось несколько новых полей и методов. Было бы просто замечательно перенести в новый класс каким-то образом все повторяющиеся функции. Это можно сделать с помощью наследования – механизма, аналога которого не было структурном программировании. Наследование не просто экономит время и силы программистов, но и позволяет ввести определенное родство между классами.
![](/html/1319/244/html_UCnnOaraXe.xg9k/htmlconvd-CzmPUH288x1.jpg)
288
Для того чтобы указать, что Klasse2 порождается классом Klasse1, надо записать
так: type
Klasse2 = class (Klasse1)
… {дополнительные элементы класса} end
Точка
Круг
Шар Цилиндр
Рис 16.3: Иерархия классов геометрических фигур
При этом в Klasse2 переходят из класса Klasse1 все его методы и поля, которые являются закрытыми.
Класс, который порождается некоторым классом, называется дочерним классом, а тот, который порождает, - родительским классом. В итоге классы образуют древовидную иерархию (см. рис 16.3).
Для того чтобы элемент класса был виден не только внутри класса, в котором он описан, и классов, которые находятся в одном модуле с ним, но также во всех наследниках этого класса, создан специальный тип доступа к элементу – protected.
Следовательно, в класс-наследник перейдут все элементы класса родителя, которые объявлены как public или protected.
Сейчас мы построим классы Punkt, Kreis и Kugel с помощью наследования. В целях экономии места код класса Kugel писать не будем.
Пример 3: Реализация классов Punkt, Kreis, Kugel с помощью наследования
|
|
///////////////////Модуль PunktU.pas //////////////////////////// |
1 |
: unit PunktU; |
|
2 |
: |
|
3 |
: interface |
|
4 |
: uses |
|
5 |
: |
SysUtils;//Для функции FloatToStr |
6 |
: type |
|
7 |
: |
Punkt = class //der Punkt - точка |
8 |
: |
private |
9 |
: |
x,y,z:real; |
10:public
11:constructor Bilde(x1,y1,z1:real); //конструктор точки
12:procedure StellPunkt(x1,y1,z1:real);
289
13:function ZurZeile:string;
14:end;
15:
16: implementation 17:
18:constructor Punkt.Bilde(x1,y1,z1:real);
19:begin
20:StellPunkt(x1,y1,z1);
21:end;
22:
23:procedure Punkt.StellPunkt(x1,y1,z1:real);
24:begin
25:x:=x1;
26:y:=y1;
27:z:=z1;
28:end;
29:
30:function Punkt.ZurZeile:string;
31:begin
32:
ZurZeile:='('+FloatToStr(x)+';'+FloatToStr(y)+';'+FloatToStr(z)+')'; 33: end;
34:
35: end.
|
|
///////////////////Модуль KreisU.pas //////////////////////////// |
1 |
: unit KreisU; |
|
2 |
: |
|
3 |
: interface |
|
4 |
: |
|
5 |
: uses |
|
6 |
: |
PunktU,SysUtils; |
7 |
: type |
|
8 |
: |
Kreis = class (Punkt) //der Kreis - круг |
9 |
: |
protected |
10:rad:real; //Поле rad будет доступно в классе Kugel
11:public
12:constructor Bilde(x,y,z,r:real);
13:procedure StellFigur(x,y,z,r:real);
14:procedure StellRad(r:real);
15:function ZurZeile:string;
16:function GibRad:real;
17:end;
18:
19:implementation
20:procedure Kreis.StellRad(r:real);
21:begin
22:if r>=0 then
23:rad:=r
24:else
25:rad:=0;
26:end;
290
27:
28:constructor Kreis.Bilde(x,y,z,r:real);
29:begin
30:inherited Bilde(x,y,z); //Вызываем унаследованный конструктор
31:StellRad(r);
32:end;
33:
34:procedure Kreis.StellFigur(x,y,z,r:real);
35:begin
36:StellPunkt(x,y,z);
37:StellRad(r);
38:end;
39:
40:function Kreis.ZurZeile:string;
41:begin
42:result:=(inherited ZurZeile) +' , Rad = '+FloatToStr(rad);
43:end;
44:
45:function Kreis.GibRad:real;
46:begin
47:result:=rad;
48:end;
49:
50: end.
/////////////////Файл проекта - ErbschaftStatisch.dpr ///////////////////
1 : program ErbschaftStatisch;
2 :
3 : {$APPTYPE CONSOLE}
4 :
5 : uses
6 : SysUtils,
7 : PunktU in 'PunktU.pas',
8 : KreisU in 'KreisU.pas',
9 : KugelU in 'KugelU.pas'; 10:
11:var
12:P:Punkt;
13:begin
14:P:=Punkt.Bilde(1,2,3);//вызываем конструктор для Punkt
15:writeln(P.ZurZeile); //вызовет ZurZeile класса Punkt
16:P.Free;
17:P:=Kreis.Bilde(1,2,4,7);
18:writeln(P.ZurZeile); //вызовет ZurZeile класса Punkt
19:writeln(Kreis(P).ZurZeile); //вызовет ZurZeile класса Kreis
20:P.Free;
21:P:=Kugel.Bilde(1,2,5,5);//вызываем конструктор для Kugel
22:writeln(P.ZurZeile); //вызовет ZurZeile класса Punkt
23:writeln(Kugel(P).ZurZeile); //вызовет ZurZeile класса Kugel
24:readln;
25:end.