Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi

.pdf
Скачиваний:
68
Добавлен:
25.04.2014
Размер:
3.16 Mб
Скачать

281

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. Наследование

Часто случается, что один класс очень похож на другой, только добавилось несколько новых полей и методов. Было бы просто замечательно перенести в новый класс каким-то образом все повторяющиеся функции. Это можно сделать с помощью наследования – механизма, аналога которого не было структурном программировании. Наследование не просто экономит время и силы программистов, но и позволяет ввести определенное родство между классами.

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.