Текст модуля, в котором описан тип tArc, создан независимо от текста модуля Figures и использует только его интерфейсную часть и комбинированный бинарный файл

Figures. dcu. Он может быть даже намного позже компиляции файла. dcu!). Но экземпляры tArc свободно могут пользоваться унаследованными виртуальными методами MoveBy, MoveTo. При этом MoveBy, MoveTo вызывают методы Show и Hide, которые для Arc свои, вновь написанные, и скомпилированы они гораздо позднее, чем модуль Figures! Это — позднее связывание: вызов методов (имени методов с его реализацией в каком либо классе) определение, к какому классу относится виртуальный метод, происходит во время выполнения программы. А статические правила имеют раннее связывание, на этапе компиляции. При этом какой тип соответствуетметоду в программе, метод такого типа и вызывается. То есть если написан вызов Row в реализации метода tDot. moveBy, то будет вызван метод tDot. Show даже в случае вызова aCircle. moveBy(…). Если же метод Show виртуальный, вызовется tCircle. Show.

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

Позднее связывание: для каждого класса создается таблица виртуальных методов (VMT) (Virtual Methods Table). VMT содержит для каждого метода указатель на адрес кода, выполняющего метод. Экземпляры объектного типа не содержат VMT (она едина для всего класса), а только ссылку на нее. Эта ссылка используется в момент вызова виртуального метода объектом. А так как каждый объект имеетссылку на метод, соответствующийсвоему классу, то вызывается метод из соответствующего класса. Как бы работала программа, если бы все методы были описаны как статические? По наследованию вызвался бы метод для прародителя tDot, и методы Show и Hide в tDot. MoveBy или tDot. MoveTo всегда воспринимались бы "настроенными" на тот тип, к которому все это относилось в момент компиляции, то есть к tDot. Значит, всегда двигалась бы точка, а не окружность или дуга!

НЕ нашли? Не то? Что вы ищете?

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

Рассмотрим, как бы себя вели экземпляры классов, описанных ниже, с теми же методами, что и в предыдущей программе, но описанные как статические:

type

tDot=

object(tLocation)

procedure Show;

procedure Hide;

procedure MoveBy(dX, dY:integer);

...

end;

tCircle=

object(tDot)

procedure Show; {перекрывается статический метод}

procedure Hide; {перекрывается статический метод}

{процедура MoveBy не перекрывается, она наследуется}

...

end;

tArc=

object(tCircle)

...

procedure Show; {перекрывается второй раз}

procedure Hide; {перекрывается второй раз}

{процедура Move не перекрывается, она наследуется}

...

end;

...

реализация методов

procedure tDot. MoveBy(dX, dY:integer);

begin

Show;

X:=X+dX;

Y:=Y+dY;

Hide;

end;

...

var aCircle:tCircle;

aArc:tArc;

...

При вызове aArc. MoveBy(5,10) вызовется метод tDot. MoveBy, т. к. он наследуется классами tCircle и tArc от класса tDot, и ни в tCircle, ни в tArc этот метод не перекрыт. В методе tDot. MoveBy вызываются методы Show и Hide. Все упомянутые методы описаны как статические. Поскольку статические методы встраиваются в исполняемый код программы "сразу", на этапе компиляции программы, они вызываются именно для того типа, в котором определены, то есть это tDot. Show и tDot. Hide. Поэтому в нашем случае tDot. Show покажет, а tDot. Hide погасит точку на экране с координатами дуги aArc. X и aArc. Y. По-видимому, это несколько не то, что бы мы хотели. Для того, чтобы двигалась дуга, процедуры Show и Hide в tDot надо пометить как виртуальные:

procedure Show;virtual;

procedure Hide;virtual;

а во всей иерархии объектов (tCircle, tArc и т. д.) надо пометить их как перекрытые:

procedure Show;override;

procedure Hide;override;

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

Как теперь будет осуществляться вызов aArc. MoveBy(5,10)? В методе tDot. MoveBy, который вызывается благодаря наследованию, вызываются методы Show и Hide из класса вызывающего их объекта, т. е. tArc. Show и tArc. Hide, поскольку методы помечены как виртуальные. В результате рисуется и перемещается дуга.

Что бы произошло, если бы в tCircle виртуальные методы Show и Hide были перекрыты (переопределены так, что рисовалась и скрывалась бы окружность), в tArc они не были еще раз перекрыты и наследовались бы от tCircle? В таблице виртуальных методов каждого класса хранятся указатели на виртуальные методы для данного класса, а также для всех его прародителей. Если виртуальный метод в классе не перекрыт, указатель для метода настраивается на метод ближайшего прародителя, в котором метод определен или перекрыт. Поэтому в случае двигалась бы окружность.

type tFigure=tDot;

Procedure moveFigure(var aFigure:tFigure, dx, dy, integer)

begin

aFigure. moveBy(dx, dy)

end;

В приведенном примере используется полиморфизм: на этапе компиляции программы неизвестно, какого типа объект aFigure вызовет метод moveBy. А поскольку реализация метода осущесталяется с помощью виртуальных методов Show и Hide, их вызовы будут осуществляться для класса, к которому относится вызывающий их объект aFigure, а не для класса tDot.

Динамические объекты. Выделение и высвобождение памяти. Деструкторы.

Указатель на объект устроен так же, как обычные указатели:

Описание указателей на объект:

type tpCircle=^tCircle; {тип "указатель на объект типа tCircle"}

var pCircle:tpCircle; {экземпляр этого типа, т. е. указатель на объект

типа tCircle}

pCircle:^tCircle; {другой вариант описания указателя на экземпляр

класса tCircle}

Создание нового объекта в "куче" (heap), на который при создании этого объекта "переключается" указатель pCircle:

New(pCircle);

Независимо от числа созданных объектов, pCircle указывает на последний из таких объектов. Если два раза сделать такой вызов, т. е. New(pCircle), то указатель на предпоследний объект потеряется, как и в случае с обычными динамическими переменными (см. Часть1).

Доступ к полям объекта:

a:=pCircle^.X;

pCircle^.MoveBy(10,21)

и т. п. При этом pCircle читается как "указатель на объект типа tCircle", а pCircle^ — как "объект типа tCircle, на который ссылается указатель pCircle".

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

pCircle^.Init(100,100,50);

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

New(pCircle, Init(100,100,50));

Указатель на динамически создаваемый экземпляр можно сразу получить, используя New как функцию. Стоит отметить, что при этом в качестве параметра оператора New ставится не имя указателя, а имя его типа. При этом динамически создается экземпляр указанного типа. Так, если:

type tpArc=^tArc;

var pArc:tpArc;

то можно записать либо

New(pArc);

pArc^.Init(100,100,20,0,90);

либо

pArc:=New(tpArc);

pArc^.Init(100,100,20,0,90);

либо

pArc:=New(tpArc, Init(100,100,20,0,90));

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

Удаление объекта (обычно динамического) производится с помощью вызова специального метода — деструктора. Конструкторов и деструкторов для одного объекта может быть несколько. Если основной конструктор в Turbo Pascal обычно называют Init, то основной деструктор — Done ("сделано"). Деструкторы, в отличие от конструкторов, могут быть виртуальными. Более того, их рекомендуется делать виртуальными, так как в них обычно отсутствуют параметры.

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

Dispose(pCircle);

Этот оператор высвобождает память, занимаемую объектом, на который указывает указатель (т. е. на который он "настроен", "переключен"). Надо отметить, что сам указатель pCircle при этом не уничтожается, а продолжает указывать на то же место в памяти. Вызов через pCircle объекта aCircle после того, как соответствующий ему участок памяти высвобожден, недопустим и обычно вызывает ошибку выполнения программы или "зависание" компьютера. Если в объекте есть динамически созданные поля или структуры, их надо убирать в определенной последовательности (иначе либо они могут остаться в памяти как "мусор", либо может произойти попытка повторно убрать уже уничтоженную часть структуры). Поэтому обычно используют специальный метод (в Borland Pascal рекомендуется называть его Done) для удаления динамического объекта. Его желательно делать виртуальным. В этом случае при наличии полиморфизма заранее неизвестно, что удаляется: прародитель или потомок, так как указатель может быть настроен либо на того, либо на другого, и это узнается только во время работы программы. А число полей и, соответственно, занимаемый размер памяти у этих объектов разный. Чтобы правильно производить удаление из памяти полиморфных объектов, метод объявляют деструктором. При этом работа метода замедляется, т. к. происходит проверка, кто вызван — прародитель или потомок. Но зато автоматически высвобождается правильное количество памяти (на основе доступа к механизму позднего связывания при вызове специального варианта Dispose с деструктором):

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16