Алгоритмические языки высокого уровня. Курс лекций
.pdfTCircle наследует метод MoveTo, но не переопределяет его. По- этому при вызове метода MoveTo экземпляром объекта TCircle он, в свою очередь, вызовет методы Show и Hide объекта TPoint, т.е. переместит точку, а не окружность.
Избежать этого можно двумя способами. Первый способ, каждый раз переопределять метод MoveTo, чтобы транслятор всегда компи- лировал его заново. Однако это не совсем удобно, поскольку эти ме- тоды ничем не будут отличаться. Второй способ – объявить методы
Show и Hide виртуальными.
При компиляции объекта, содержащего виртуальные методы, соз-
дается так называемая таблица виртуальных методов (ТВМ), со-
держащая адрес точки входа каждого из виртуальных методов, а в месте вызова такого метода ставится ссылка на ТВМ. Все это проис- ходит уже на этапе выполнения программы и поэтому называется поздним (динамическим) связыванием. Ранним связыванием называ- ется процесс статического связывания методов с реализациями (эк- земплярами) объектов. Раннее связывание осуществляется на этапе компиляции для всех статических методов. Для объявления вирту- ального метода используется зарезервированное слово (директива)
virtual.
Новые объявления методов TPoint и TCircle:
Type
TPoint = object(TLocation) Clr : Byte; {Цвет}
Visib : Boolean; {Видимость}
Constructor Init (InitX, InitY:Word; InitColor:Byte); {Переопределяем метод инициализации - добавляем цвет} Function GetColor : Byte; {Возвращает цвет} Procedure Show; virtual;
Procedure Hide; virtual; Procedure IsVisib : Boolean;
Procedure ChangeColor(NewColor : Byte); {Меняет цвет} Procedure MoveTo(NewX, NewY:Word);
{Перемещает в новую позицию} Destructor Done; virtual;
end;
TCircle = object(TPoint) R : Word;
Constructor Init(InitX, InitY, InitR : Word; InitC : Byte);
Procedure Show; virtual; Procedure Hide; virtual; Destructor Done; virtual;
141
end;
Объявление виртуального метода в каком-либо родительском объектном типе накладывает следующие ограничения на все его до- черние типы:
•все методы дочерних типов, одноименные с виртуальными ро- дительскими, обязаны быть также виртуальными (нельзя переопре- делить виртуальный метод статическим);
•заголовки всех реализаций одного и того же виртуального мето- да должны быть полностью идентичными, включая количество фор- мальных параметров и их типы;
•каждый объектный тип, имеющий виртуальные методы, обязан иметь конструктор.
Замечание: при объявлении метода Init используется constructor, Done – destructor. Дело в том, что таблица виртуальных методов изначально не содержит конкретных адресов точек входа. Перед использованием любого из виртуальных методов ее надо заполнить. Делает это специальный метод-конструктор. Метод-конструктор – это разновидность метода-процедуры. Син- таксически он отличается только использованием служебного слова constructor вместо procedure. Однако это приводит к тому, что при компиляции к этому методу добавляется так называемый пролог, код которого как раз и «расставляет» в ТВМ правильные адреса виртуальных методов.
Конструкторов в объекте может быть сколько угодно, один из конструкторов обязательно должен быть вызван перед вызовом пер- вого виртуального метода (иначе программа попросту «зависнет»), и конструктор сам не может быть виртуальным. Деструктор в отличие от конструктора вполне может быть виртуальным.
Type {Статический полиморфизм}
A=object;
Procedure out;
End;
B=object A
Procedure out;
End;
Procedure A.out;
Begin
Write(“A”);
End;
Procedure B.out;
Begin
142
Write(“B”);
End;
Var A1: A; B1: B; Begin
A1.out;
B1.out;
End. Результатом работы программы будет: А B Type {Динамический полиморфизм} A=object;
Procedure out; Function outs:string;
End; B=object A
Function out:string; End;
Procedure A.out; Begin
outs;
End;
Function A.outs; Begin
Outs:= “A”; End;
Function B.outs; Begin
Outs:= “B”; End;
Var A1: A; B1: B; Begin
A.out;
B.out;
End. Результатом работы этой программы будет: A А Type
A=object; Procedure out;
Function outs:string; Constructor init;
End; B=object A
Function out:string;virtual; Constructor init;
End;
Constractor A.Init Begin
End.
Procedure A.out; Begin
outs;
End;
143
Function A.outs;
Begin
Outs:= “A”;
End;
Constractor В.Init
Begin
End.
Function B.outs;
Begin
Outs:= “B”;
End;
Var A1: A; B1: B;
Begin
A.Init;
B.Init;
A.out;
B.out;
End. Результатом работы этой программы будет: A B
7.5. Использование экземпляров объектов
Поскольку объект – это тип данных, то для работы с ним нужно объявить переменную этого типа. Такая переменная в рамках ООП называется экземпляром объекта. Экземпляр объекта может быть объявлен для любого объектного типа иерархии (как для родителя, так и для потомка). Более того, может быть объявлено несколько эк- земпляров одного и того же объекта. Каждый из таких экземпляров получает «свой» набор свойств. Они ничего «не знают» друг о друге, т.е. являются полностью независимыми (по крайней мере, должны таковыми быть).
Экземпляр объекта описывается в разделе описания переменных программы:
Var
<имя переменной (экземпляр объекта)>:<объектный тип>;
Объявлять можно как единичные экземпляры объектов, так и мас- сивы объектов, записи, содержащие объекты, и даже объекты, со- держащие объекты (правда, только в разделе описания типов Type). Единственное, чего нельзя делать – это объявлять файлы объектов:
Var
a: Array [1..10] of TPoint; {Массив объектов - точек} b = record {Запись, содержащая два объекта}
p1,p2: TPoint; end;
f : file of TPoint; {Файл объектов - так нельзя}
144
Type
Rect = object {Объект, поля которого - объекты} a,b: TLocation;
end;
Описание экземпляра объекта TPoint (т.е. точки на экране):
Var Point1: TPoint;
После описания экземпляра объекта программе станут доступны- ми его информационные поля и методы, т.е. в программе можно чи- тать и изменять значения полей экземпляра объекта, а также вызы- вать его методы. Обратиться к полю экземпляра объекта можно сле- дующим способом: указать имя экземпляра, затем через точку имя поля. После чего с полем можно обращаться как с обычной перемен- ной, например присвоить ему какое-то значение:
Point1.X := 65;
Однако считается плохим стилем программирования прямое об- ращение к полям экземпляра объекта. Предполагается, что програм- ма не должна ничего «знать» об атрибутах объекта, она только может «просить» экземпляр объекта показать или изменить свои атрибуты за счет использования соответствующих методов. Иначе говоря, про- грамма может только вызывать методы экземпляра объекта. Вызов метода осуществляется синтаксически по тем же правилам, что и вы- зов «обычной подпрограммы», только перед именем метода надо обязательно написать через точку имя экземпляра объекта:
Point1.Init(150,100,Blue); – создан объект – точка с коор-
динатами (X=150,Y=100) синего цвета (невидимая). Для «проявле- ния» ее на экране необходимо вызвать метод Show:
Point1.Show;
Для перемещения этой точки в новую позицию необходимо вы- звать метод MoveTo, для получения текущих координат – GetX,
GetY или GetCoords и т.п.
7.5.1. Объекты и динамическая память
Экземпляры объектов можно размещать в динамической памяти (куче). Работа с кучей применительно к объектам почти не отличает- ся от работы с кучей применительно к обычным данным. Точно так же необходимо объявить ссылку на объект (переменную ссылочного типа), выделить для нее память в куче, а в конце работы эту память освободить.
Размещение динамической памяти экземпляра объекта TCircle:
145
Type
PCircle = ^TCircle; Var
Circ1 : PCircle;
...
Begin
Circ1 := New(PCircle); или New(Circ1);
Для работы с экземпляром объекта, расположенного в куче, необ- ходимо проводить (как обычно) операцию разыменовывания указа- теля:
Circ1^.Init(150,200,40,Blue);
Существует расширенный синтаксис процедуры (и функции) New для выделения памяти экземпляру объекта с одновременным вызо- вом метода-конструктора:
Сirc1 := New(PCircle,Init(150,200,40,Blue));
или
New(Сirc1,Init(150,200,40,Blue));
При работе с динамическими объектами надо очень серьезно под- ходить к освобождению памяти. Ведь и сам объект в качестве своих полей может содержать указатели и ссылки. Память, занятая этими динамическими данными, должна освобождаться до вызова процеду- ры Dispose для самого объекта. Если память, занятая полями, осво- бождается в методе Done, то этот метод должен быть вызван до уда- ления экземпляра объекта из кучи. То, что экземпляр объекта- родителя и экземпляр объекта-потомка являются совместимыми по типу «сверху вниз» (значение экземпляра объекта-потомка может быть присвоено экземпляру объекта-предка с потерей «лишних дан- ных», но не наоборот), при работе с кучей также может привести к неверному освобождению памяти (может быть освобождена «чужая» память). В связи с этим синтаксис процедуры Dispose тоже расши- рен и позволяет вызывать деструктор объекта до его удаления из кучи:
Dispose(Сirc1,Done);
При таком написании сначала вызывается деструктор, а затем вы- свобождается память. Назначение деструктора как разновидности процедуры – следить за тем, чтобы высвобождалась соответствую- щая память. Деструктор – это, как и конструктор, специальный вид метода, который по-другому еще называют «сборщиком мусора».
146
7.5.2. Объекты и модули
Свойства объектов (наследование, полиморфизм) позволяют соз- давать библиотеки объектов для их дальнейшего использования, так как структура описания объектов идеально соответствует структуре модуля: в модуле есть интерфейсная часть и раздел реализации, и при описании объектного типа сначала объявляется заголовок мето- дов, а затем их реализация.
Для того чтобы разработать библиотеку объектов, необходимо создать модуль, в интерфейсной части которого описать сами объ- ектные типы (те, которые будут доступны после подключения моду- ля), а в разделе реализации модуля реализовать описания их методов.
Создание модуля графических объектов GraphObj.
Unit GraphObj; {Модуль графических объектов} INTERFACE {Интерфейсная часть}
Type
PLocation = ^TLocation; TLocation = object
X,Y: Word; {Координаты позиции на экране} Constructor Init(InitX, InitY: Word); {Инициализация
объекта}
Function GetX: Word; {Возвращает координату Х} Function GetY: Word; {Возвращает координату Y}
Procedure GetCoords(Var CoordX, CoordY: Word); {Возвращает обе координаты}
Destructor Done; virtual; {Уничтожает объект} End;
PPoint = ^TPoint;
TPoint = object(TLocation) Clr: Byte; {Цвет}
Visib: Boolean; {Видимость}
Constructor Init(InitX,InitY:Word; InitColor:Byte); {Переопределяем метод инициализации-добавляем цвет}
Function GetColor: Byte; {Возвращает цвет} Procedure Show; virtual;
Procedure Hide; virtual; Function IsVisib: Boolean;
Procedure ChangeColor(NewColor:Byte); {Меняет цвет} Procedure MoveTo(NewX, NewY: Word);
{Перемещает в новую позицию} Destructor Done; virtual;
end;
PCircle = ^TCircle; TCircle = object(TPoint) R: Word; {Радиус}
Constructor Init(InitX,InitY,InitR:Word;InitC:Byte);
147
Procedure Show; virtual; Procedure Hide; virtual; Destructor Done; virtual;
end;
IMPLEMENTATION {Раздел реализации}
Uses Graph; {Подключаем графическую библиотеку} Constructor TLocation.Init;
Begin
X := InitX;
Y := InitY end;
Function TLocation.GetX: Word; begin
GetX := X end;
Function TLocation.GetY: Word; begin
GetY := Y end;
Procedure TLocation.GetCoords; begin
CoordX := GetX;
CoordY := GetY end;
Destructor TLocation.Done; begin
end;
Constructor TPoint.Init; begin
inherited Init(InitX, InitY); Clr := InitColor;
Visib := False end;
Function TPoint.GetColor: Byte; begin
GetColor := Clr end;
Procedure TPoint.Show; begin
PutPixel(X, Y, Clr); Visib := True
end;
Procedure TPoint.Hide; begin
PutPixel(X, Y, GetBkColor); Visib := False
end;
Function TPoint.IsVisib: Boolean; begin
IsVisib := Visib
148
end;
Procedure TPoint.ChangeColor; begin
Clr := NewColor;
If IsVisib then Show end;
Procedure TPoint.MoveTo; Var St: Boolean;
begin
St := IsVisib; Hide;
X := NewX;
Y := NewY;
If St then Show end;
Destructor TPoint.Done; begin
Hide;
Clr := GetBkColor end;
Constructor TCircle.Init; begin
inherited Init(InitX, InitY, InitC); R := InitR
end;
Procedure TCircle.Show; begin
SetColor(Clr);
Circle(X, Y, R); Visib := True
end;
Procedure TCircle.Hide; begin
SetColor(GetBkColor); Circle(X, Y, R); Visib := False
end;
Destructor TCircle.Done; begin
inherited Done; R := 0
end;
{Пустая секция инициализации} END.
Использование ООП и хороший стиль программирования предпо- лагают создание объектов-программ (объектов-приложений). Такой объект (назовем его Application) должен уметь выполнять три
149
действия: инициализация (Init), выполнение основной работы (Run) и завершение работы (Done).
Пример программы
Описание: создание приложения, в котором по экрану случайным образом будет двигаться точка и окружность до нажатия какой-либо клавиши. В приложении созданы экземпляры объектов TPoint (эк- земпляр объекта TPoint размещается в области динамической па-
мяти) и TCircle.
Program AppExample;
Uses Graph, CRT, GraphObj; Type
TApplication = object Procedure.Init; Procedure.Run; Procedure.Done;
end; Var
Application: TApplication; p: PPoint;
c: TCircle;
Procedure TApplication.Init; Var
D, R, MaxX, MaxY: Integer; Begin
{Инициализация графического режима} D := Detect;
InitGraph(D, R, 'C:\BP'); SetBkColor(White);
ClearDevice;
{Создаем точку и окружность} MaxX := GetMaxX;
MaxY := GetMaxY; Randomize;
New(p,Init(Random(MaxX), Random(MaxY),Red)); p^.Show;
c.Init(Random(MaxX-40)+20,Random(MaxY-40)+20,20,Blue); c.Show
End;
Procedure TApplication.Run; Var
MaxX, MaxY: Integer; Begin
MaxX := GetMaxX;
MaxY := GetMaxY; Repeat
p^.MoveTo(Random(MaxX),Random(MaxY));
150
