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

Информатика в техническом университете / Информатика в техническом университете. Объектно ориентированное программирование

.pdf
Скачиваний:
105
Добавлен:
06.03.2018
Размер:
9.48 Mб
Скачать

2. Средства ООП в Borland Pascal 7.0

Полиморфные методы, для которых применяется позднее связывание, называют виртуальными.

Для описания виртуальных методов используется служебное слово vir­

tual:

Procedure <имя метода>(<список параметров>); virtual;

Function <имя метода> (<список параметров>):<тип функции>; virtual;

При использовании виртуальных методов должны выполняться следующие правила.

1. Если в некотором классе метод описан как виртуальный, то все производные классы, включающие метод с тем же именем, должны описать этот метод как виртуальный. Нельзя заменить виртуальный метод статическим.

2.Порядок расположения, количество и типы формальных параметров в одноименных виртуальных методах должны оставаться неизменными.

3.Класс, содержащий виртуальные методы, должен включать спевд1альный статический метод - конструктор. Для этого метода слово Procedure в объявлении и реализации должно быть заменено словом Constructor. Конструктор неявно выполняет настройку механизма позднего связывания (обеспечивает связь объекта с ТВМ - раздел 1.6). Этот метод долэюен быть вызван до первого обращения к виртуальному методу, иначе происходит «зависание» компьютера. Как правило, конструктор используют для иншдиализащш полей объекта и, соответственно, называют Init.

Вызов виртуальных методов осуществляется через ТВМ, что требует дополнительного времени. В результате программа будет работать медленнее, чем при использовании статических методов. Однако использование виртуальных методов позволяет существенно увеличить гибкость описания классов.

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

1) полиморфный метод вызьюается из статического метода родительского класса;

2)в программе используется процедура или функция с параметрами типа класса, которой в качестве фактического параметра может быть передан объект производного класса (процедура с полиморфным объектом)., и эта функция вызьюает полиморфный метод для объекта-параметра;

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

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

70

2.3. Полиморфизм

Продемонстрируем использование виртуальных методов, несколько изменив иерархию классов примера 2.4.

Пример 2.5. Вызов виртуальных методов из методов базового класса.

Если при разработке иерархии классов Win - Symb - Lot учесть, что окно всегда рисуется для вывода символов, то в описание класса Win стоит включить метод Run, который изображает окно и выводит в него символы. Этот метод должен вызывать методы изображения окна МакеWin и вывода символов в окно Print. Метод МакеWin в классах-потомках не переопределяется (статический метод). Метод Print в классах Symb и Lot переопределяется (полиморфный метод). В классе Win метод Print не предусмотрен, поэтому добавим в этот класс абстрактный («пустой», не выполняющий никаких действий) метод Print.

Метод Run будет наследоваться классами Symb и Lot. Конкретный тип объекта, для которого вызывается метод Run, будет определен только на этапе выполнения, значит и нужный аспект полиморфного метода Print можно будет определить только на этапе вьшолнения программы, и, следовательно, метод Print должен быть объявлен виртуальным (случай 1 из рассмотренных выше).

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

Program MetVirt;

Uses Crt;

Type

Win = Object {родительский класс Окно}

XlJlX2J2,Cf:byte;

Constructor Init(Xnl Yn],Xn2, Yn2, Cfn:byte);

Procedure MakeWin;

{изображеьше окна}

Function GetSizeX: byte;

{определение размера окна по X}

Function GetSizeY: byte;

{определение размера окна по Y}

Procedure Run;

 

{изображение окна с текстом}

Procedure Print; virtual; {абстрактный виртуальный метод}

End;

 

 

 

 

Constructor WinJnit;

 

 

Begin Xl:=Xnl;

Yl:=Ynl; X2:=Xn2; Y2:=Yn2; Cf:=Cfii; End;

Procedure Win.MakeWin;

 

 

Begin

Window(XlJlX2J2);Textbackground(Cfi;ClrscrEnd;

Function Win. GetSizeX;

Begin

GetSizeX: =X2'X1+1 End;

Function Win.GetSizeY;

Begin

GetSizeY: =Y2-Y1+1 End;

Procedure Win.Run;

 

 

Begin MakeWin;

{вызов статического метода изображения окна}

Print

{вызов аспекта виртуального полиморфного метода}

End;

 

 

 

 

71

 

 

2. Средства ООП в Borland Pascal 7.0

 

Procedure Win.Print; Begin

End; {резервный - «пустой» метод}

Type

 

 

 

 

 

 

 

Symb = Object(Win)

{класс-потомок «символ»}

 

 

Cs, Col,Line:byte; Sym:char;

 

 

 

Constructor Init(Xnl, Ynl,Xn2, Yn2,Cfn,Csn,ClLn:byte;Sm:Char);

 

Procedure Print; Virtual;

{вывод символа}

 

End;

 

 

 

 

 

 

Constructor Symb.Init;

 

 

 

 

Begin

Win.Init(Xnl YnlXn2,

Yn2, Cfii);

 

 

End;

Cs:=Csn; Col:-CI; Line:=Ln; Sym:=Sm;

 

 

 

 

 

 

 

 

Procedure Symb.Print;

 

 

 

 

 

Begin TextColor(Cs); Gotoxy(Col,Line); Write(Sym) End;

 

Type

 

 

 

 

 

 

 

Lot = Object(Symb)

{класс-потомок Lot}

 

 

N:Word;

 

 

 

 

 

 

Constructor Init

 

 

 

 

 

 

(Xnl, YnlXn2,

Yn2,Cfii,Csn,Cl,Ln:Byte;Sm:Char; Nkword);

 

Procedure Print; virtual;

 

 

 

End;

 

 

 

 

 

 

Constructor

Lotlnit;

 

 

 

 

 

Begin SymbJnit(Xnl,

Ynl,Xn2, Yn2,Cfn,Csn,ClLn,Sm);

N: = Nk; End;

Procedure Lot.Print;

 

 

 

 

 

Var i:byte;

 

 

 

 

 

 

Beginfori:=l

toNdo

begin

Symb.Print;

inc(Col); inc(Line) end; End;

Var

VI.Win; V2:Symb; V3:Lot;

 

 

 

Begin

Vl.Init(20,5,40,15,12);

 

 

{конструировать объект VI}

 

V2.Init(10,3,60,20,3,1,30,10, 'A');

{конструировать объект V2}

 

V3.Init(l,1,80,25,5,1,40,12,

'B\10);

{конструировать объект V3}

 

VI.Run; {вызвать «пустой» аспект метода Print}

Readkey;

 

V2.Run; {вызвать метод Print класса Symb}

Readkey;

End.

V3.Run; {вызвать метод Print класса Lot}

Readkey;

 

 

 

 

 

 

 

Использование процедур с полиморфным объектом предполагает передачу переменных типа класса в процедуру или функцию в качестве параметров. При этом описывается параметр родительского типа, а реально может быгь передан объект не только родительского класса, но и любого из классов-потомков.

Пример 2.6. Использование процедуры с полиморфным объектом.

Вместо метода Run можно описать процедуру с полиморфным объектом РоЮЬ, в которой параметром является переменная типа Win. Такой подход также дает

72

2.3. Полиморфизм

ВОЗМОЖНОСТЬ использовать процедуру РоЮЬ, а в ней конкретно методы MakeWm и Print, для класса-родителя ^\^п и всех его потомков. При этом нужный аспект полиморфного метода Print будет определен по типу переданного в процедуру параметра (VI,V2 или V3).

Если убрать слово virtual в методе родителя Print, то при обращении к любому потомку метод Print будет взят родительский (в данном случае «пустой»).

Program РгРоЮЪ; Uses crt;

Type

Win = Object {родительский класс Окно}

Xl,YlX2J2,Cf:byte;

Constructor Init(Xnl, Ynl,Xn2, Yn2, CJh:byte); Procedure MakeWin; {изображение окна}

Function GetSizeX: byte; {определение размера окна по X} Function GetSizeY: byte; {определение размера окна по Y} Procedure Print; virtual; {абстрактный виртуальный метод}

End;

Constructor WinJnit;

Begin XI:=Xnl; Yl:=YnI;

X2:=Xn2; Y2-Yn2;

Cf:=CJh; End;

Procedure Win.MakeWin;

 

 

 

 

 

Begin

Window(XlJlX2J2);

 

Textbackground(CJ); Clrscr End;

Function Win.GetSizeX;

Begin

GetSizeX: =X2'Xm

End;

 

Function Win.GetSizeY;

Begin

GetSizeY:=Y2'Y1-^1 End;

 

Procedure Win.Print; Begin

End;

 

 

{

 

 

 

 

 

}

{

Описание классов Symb и Lot из примера 2.5

}

{

 

 

 

 

 

}

Var VI: Win; V2:Symb; V3:Lot\ {экземпляры родственных классов} {Процедура с полиморфным объектом Obj}

Procedure PolOb(Var Obj: Win); Begin

Obj.MakeWin; {вызвать обычный статический метод} Obj.Print {вызвать виртуальный полиморфный метод}

End;

 

 

Begin

 

 

VI. init(20,5,40,15,12);

{конструировать объект VI}

V2,init(10,3,60,20,3,1,30,10, 'А ) ;

{конструировать объект V2}

V3.init(l,1,80,25,5,1,40,12, 'В\10);

{конструировать объект V3}

PolOb(Vl); {вьшолнить с объектом VI}

Readkey;

PolOb(V2); {вьшолнить с объектом V2}

Readkey;

PolOb(V3); {вьшолнить с объектом V3}

Readkey;

End

 

 

2. Средства ООП в Borland Pascal 7.0

Механизм сложного полиморфизма необходимо использовать и при работе с объектами классов Win - Symb - Lot через указатели (случай 3 из рассмотренных вьппе). В этом варианте указателю на объект класса-родителя присваивается адрес объекта класса-потомка, а затем для него вызьюается полиморфный метод или процедура с полиморфным объектом:

{

 

}

{

Описание класса Win и его наследников

}

{

Symb и Lot из примера 2.5

}

Туре W=^Win; {указатель на родительский класс}

 

Var

V:W; V2:Symb; V3:Lot;

 

Procedure PolOb(VarObj:W); Begin Obj\MakeWin; Obj\Print End; Begin

New(V); V14nit(20,5,40J5,5);

V2.Init(10,3M20,3J,30J0, 'A');

V3.Init(l,lM25,5,3,40,12/B\]0); PolOb(Vl); Readkey;

V1:=@V2; {передать адрес 1-го потомка}

PolOb(Vl); {вьюодит: A} Readkey; V1:=@V3; {передать адрес 2-го потомка}

PolOb(Vl); {выводит: ВВВВВВВВВВ} Readkey;

End

Тип полиморфного объекта в программе можно определить с помощью функции lypeOf:

ТУреОГ (<имя объекта>): Pointer ;

или

TypeOf (<имя класса>): Pointer ;

Данная функция возвращает указатель на ТВМ для конкретного объекта или класса. Она применима только к классам, включающим виртуальные методы.

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

if TypeOf (Self)=ТуреО/(<ИмяЭкземш1яра>) then . . . ;

Чтобы определить фактический размер объекта, можно применить функцию SizeOf:

SizeOF (<имя объекта>): integer;

или

SizeOF (<имя класса>): integer;

74

2.4. Динамические объекты

Примечание. Для проверки корректности экземпляров программа должна компилироваться в режиме {5/?+}. При этом генерируется вызов подпрограммы проверки правильности ТВМ перед каждым вызовом виртуального метода. Это замедляет работу программы, поэтому рекомецг^ется включать {$R+) только во время отладки.

По правилам Borland Pascal для инициализации полей статических объектов можно использовать типизированные константы (раздел 2.1). Следовательно, в программе примера 2.5 объекты можно было бы объявить следующим образом:

Const Vl:Win=(Xl:20;Yl:5;X2:40;Y2:15:Cf:12);

V2:Symb=(Xl:10;Yl:3;X2:60;Y2:20;Cf:3;

Cs:l;Col:30;Line:10;Sym: 'A');

V3:Lot=(Xl:l;Yl:l;X2:80;Y2:25;Cf:5;

Cs:l;Col:40;Line:12;Sym: 'B \'N:10);

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

2.4. Динамические объекты

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

Var <имя указателя> ; ^<имя класса>;

Для размещения объекта в динамической памяти используют процедуру или функцию New:

процедура

New (<имя указателя>);

функция

<имя yкaзaтeля>:=New(<тип «указатель на класс»>).

Если динамический объект использует виртуальные методы класса, то до обращения к этим методам он должен вызвать конструктор (раздел 2.3):

<имя указателя>^.<имя конструктора>(<список параметров>).

В Borland Pascal 7.0 имеется возможность использования расширенного варианта процедуры и функции New, который позволяет в одной операции

75

2. Средства ООП в Borland Pascal 7.0

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

процедура

New (<имя указателя>,

<имя конструктора>(<список параметров>));

функция

<имя указателя>: = New(<THn «указатель на класс»>,

<имя конструктора>(<список параметров>))

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

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

0!$ро$е(<имя указателя>);

Если класс объекта включает виртуальные методы, то при уничтожении объекта о б я з а т е л ь н о должен вызываться специальный метод - деструктор. Так же, как для конструктора, для деструктора служебное слово Procedure должно заменяться служебным словом Destructor, что подразумевает выполнение некоторых специальных действий при уничтожении объекта: деструктор определяет реальный размер объекта и передает его процедуре Dispose. Если конструктор рекомендуется назьшать Init, то деструктор обычно называют Done. Деструкторы могут наследоваться, бьггь статическими или виртуальными. Обычно они используются для освобождения памяти, вьщеленной под динамические поля объекта.

Деструктор можно вызвать в расширенном формате процедуры Dispose:

Dispose (<имя указателя>,<имя деструктора>);

Для динамических объектов имеет смысл объявлять зарезервированный виртуальный деструктор, пусть даже и пустой:

Destructor <имя класса> . Done; virtual;

Begin End;

Контроль распределения памяти. Если памяти для размещения динамического обьекта или диЕ1амических полей окажется недостаточно, то вызов процедуры или функции New приведет к возникновению ошибки времени вьшолнения с номером 203 (Heap overflow error - «переполнение динамической памяти»).

76

2.4. Динамические объекты

Аварийного завершения программы в данном случае можно избежать, определив собственную функцию обработки данной ошибки и указав ее адрес среде программирования Borland Pascal:

Function HeapFunc(size: Word).integer; far; Begin HeapFunc:=l; end;

HeapError: =@HeapFunc;

Тогда при обнаружении нехватки памяти процедура или функция New вернет указатель со значением nil, что можно проверить в программе.

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

В примере 2.7 показано, как можно организовать динамический объект с динамическими полями.

Пример 2.7. Динамический объект с динамическим полем и контролем выделения памяти. Разработаем класс WinD, который попрежнему включает в себя поля и методы для создания окна с заданными цветами фона и символов, но для хранения параметров объекта использует массив (вектор атрибутов окна), память под который вьщеляется динамически. Данный массив состоит из 6 элементов, где элементы с индексами 1 - 4 - координаты окна, а элементы с индексами 5 и 6 - цвет фона и цвет символов окна.

Program DinObj;

Uses Crt;

Type

APtr = ^Vaw; {объявить указатель на массив}

Vow = array [7.. б] of byte;

WinPtr = ^WinD; {указатель на объект класса}

WinD = Object

A:APtr;

{указатель на вектор атрибутов окна}

Constructor Init(An: Vow);

{конструктор}

Destructor Done; virtual;

{деструктор}

Procedure Make Win;

{создание окна}

Procedure ColorWin;

{установка цвета фона и символов}

End;

 

Constructor WinD.Init;

 

Begin

 

77

2. Средства ООП в Borland Pascal 7.0

New(A); {разместить поле А в динамической памяти}

ifA-nil

then {при неудаче освободить память}

begin

WinD.Done; Fail

end;

 

А^:=Ап; {иншщализащы массива атрибутов}

End;

 

 

 

 

Destructor WinD.Done;

 

 

Begin

ifAonil

then Dispose (A); {освобождаем поле A} End;

Procedure WinD.MakeWin; {метод создания окна}

Begin

mndow(A''flJA^f2J,A''f3J,A^f4J)

End;

Procedure WinD.ColorWin;

 

 

Begin

TextbackGround(A''[5]); TextColor(A''f6J); Clrscr End;

Function HeapFunc(size:Word):integer; far;

Begin HeapFunc:=l; end;

Var V: WinPtr;

{указатель на динамический объект}

Const

 

 

 

 

Am: Vaw=(l, 1,80,25,4,1);

{координаты верхнего левого и нижнего

правого угла окна, цвет фона и символов}

Begin

HeapError: =@HeapFunc; {подключить функщпо обработки ошибки}

Clrscr;

New(VfInit(Am)); {разместить динамический объект} if V-nil then Halt(2); {если неудачно, то выйти}

V^.MakeWin; {вызвать методы для динамического объекта}

V\ColorWin;

Dispose(V,Done); {уничтожить объект и освободить поля}

Readkey; End

Для контроля размещения динамических полей статического объекта осуществляется проверка возвращаемого значения конструктора. Если это значение равно false, то в конструкторе была выполнена директива fail.

Пример 2.8. Статический объекте динамическим полем и контролем выделения памяти.

Program DinObj;

{

 

}

{

Описание класса WinD и процедуры обработки

}

{

ошибок из примера 2.7

}

Var

W :WinD; {статический объект}

 

Const

 

 

А: Vaw=(l, 1,80,25,4,1); {координаты верхнего левого и нижнего правого угла окна, цвет фона и символов}

78

2.4. Динамические объекты

Begin

HeapError: -@HeapFunc; {подключить функцию обработки ошибки}

Clrscr;

if not WJnit(A) then Halt(2); {если неудачно, то выйти}

W.MakeWin; {вызвать методы для динамического объекта}

W.ColorWin;

W.Done; {освободить память, отведенную под динамическое поле}

Readkey;

End.

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

Пример 2.9. Использование динамических объектов (программа «Снежинки»). Допустим, требуется создать программу, в которой случайным образом генерируются объекты в виде разноцветных снежинок (такая программа может выполнять функцию заставки на экране компьютера). Необходимо предусмотреть возможность изменения состояния объектов, в частности: снежинки должны перемещаться по экрану, менять свои размеры и, достигнув определенного положе1шя, исчезать (рис. 2.3).

При объектной декомпозиции мы получаем три типа объектов: псевдо­ объект - Основная программа и множество объектов - снежинок, которые делятся на Изменяющиеся снежинки и Падающие снежинки (рис. 2.4).

# #f*

'^F

' #

^ '

b

v ^

^

Рис. 2.3. Вид экрана при выполнении программы «Снежинки»: а - снежинка меняет размер; b - снежинка перемещается вниз

79