- •Процедуры и функции
- •Объявление процедур и функций без параметров
- •Определение процедуры, основные особенности работы
- •Определение функции, основные особенности работы
- •Области видимости процедур и функций
- •Глобальные и локальные переменные
- •Оформление исходного текста процедур и функций
- •Параметры-значения и особенности работы с ними
- •Параметры-переменные и особенности работы с ними
- •Параметры-константы и особенности работы с ними
- •Открытые параметры-массивы и особенности работы с ними
- •Бестиповые параметры и особенности работы с ними
- •Процедурный тип, определение, область применения
- •Явная и неявная рекурсия
- •Типизированные константы
- •Определение файла, общие процедуры и функции для работы с ними
- •Текстовые файлы, особенности,процедуры и функции для работы с ними
- •Типизированные файлы, особенности,процедуры и функции для работы с ними
- •Нетипизированные файлы, особенности,процедуры и функции для работы с ними
- •Модули. Структура модулей Паскаль
- •Особенности использования, видимость содержимого
- •Компилляция модуля(3 режима)
- •Стандартные модули Паскаль
- •Указатели
- •Основные понятия, типы указателей, объявление
- •Способы задания значений переменных
- •Основные процедуры работы с указателем
- •Работа с диманической памятью
- •Основные ошибки при работе с указателями
- •Объекты
- •Ооп, основные понятия и определения
- •Достоинства и недостатки
- •Объявление объектного типа, поля, методов
- •Области видимости в модели объектов Паскаль
- •Наследование
- •Полиморфизм, виртуальные методы
- •Использование экземпляров объекта
- •Динамическая память и деструкторы
Полиморфизм, виртуальные методы
Создадим еще один дочерний объектный тип - потомок объекта TPoint, который будет представлять собой окружность. X и Y соответственно превращаются в координаты центра, и добавляется радиус. Само собой разумеется, придется переопределить методы инициализации, прорисовки и скрытия.
Type
TCircle = object(TPoint)
R : Word;
Procedure Init(InitX, InitY, InitR:Word; InitC:Byte);
Procedure Show;
Procedure Hide;
Procedure Done;
end;
Procedure TCircle.Init(InitX, InitY, InitR : Word; InitColor : Byte);
begin
inherited Init(InitX, InitY, InitC);
R := InitR
end;
Procedure TCircle.Show;
begin
Graph.SetColor(Clr);
Graph.Circle(X, Y, R);
Visib := True
end;
Procedure TCircle.Hide;
begin
Graph.SetColor(Graph.GetBkColor);
Graph.Circle(X, Y, R);
Visib := False
end;
Procedure TCircle.Done;
begin
inherited Done;
R := 0
end;
Таким образом, мы получили два объекта, методы которых Show и Hide делают одно и то же, но разными способами (полиморфизм).
Но вот непредвиденные последствия. Если мы создадим экземпляр этого объекта, проинициализируем его, а затем попытаемся переместить вызовом метода MoveTo, который был унаследован, то переместится точка, а не окружность. Связано это с тем, что при компиляции в машинный код вместо вызова подпрограммы транслятор подставляет адрес точки входа в эту подпрограмму. Это же справедливо и для методов, поэтому при трансляции метода MoveTo объекта TPoint будут подставлены адреса точек входа методов Show и Hide объекта TPoint. Объект же TCircle наследует метод MoveTo, но не переопределяет его. Поэтому при вызове метода MoveTo экземпляром объекта TCircle он, в свою очередь, вызовет методы Show и Hide объекта TPoint, то есть - переместит точку, а не окружность.
Избежать этого можно двумя способами. Во-первых, каждый раз переопределять метод MoveTo, чтобы транслятор всегда компилировал его заново. Однако это не совсем удобно, поскольку эти методы ничем не будут отличаться. Второй способ - объявить методы Show и Hide виртуальными.
При компиляции объекта, содержащего виртуальные методы, создается так называемая таблица виртуальных методов (ТВМ), содержащая адрес точки входа каждого из виртуальных методов, а в месте вызова такого метода ставится ссылка на ТВМ. При обращении к виртуальному методу компьютер сначала "смотрит", экземпляр какого именно объекта обратился к этому методу, затем "ищет" адрес точки входа виртуального метода именно этого объекта и запускает его. Все это происходит уже на этапе выполнения программы и поэтому называется поздним (динамическим) связыванием. Ранним связыванием называется процесс статического связывания методов с реализациями (экземплярами) объектов. Раннее связывание осуществляется на этапе компиляции для всех статических методов. Для объявления виртуального метода используется зарезервированное слово (директива) virtual.
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;
end;
Объявление виртуального метода в каком-либо родительском объектном типе накладывает следующие ограничения на все его дочерние типы:
все методы дочерних типов, одноименные с виртуальными родительскими, обязаны быть также виртуальными (нельзя переопределить виртуальный метод статическим);
заголовки всех реализаций одного и того же виртуального метода должны быть полностью идентичными, включая количество формальных параметров и их типы;
каждый объектный тип, имеющий виртуальные методы, обязан иметь конструктор.
Обратите внимание на то, что, кроме добавления слова virtual, изменилось и еще кое-что: при объявлении метода Init появилось незнакомое слово constructor, а метод Done превратился в destructor.
Дело в том, что таблица виртуальных методов изначально не содержит конкретных адресов точек входа. Перед использованием любого из виртуальных методов ее надо заполнить. Делает это специальный метод-конструктор. Метод-конструктор - это разновидность метода-процедуры. Синтаксически он отличается только использованием служебного слова constructor вместо procedure. Однако это приводит к тому, что при компиляции к этому методу добавляется так называемый пролог, код которого как раз и "расставляет" в ТВМ правильные адреса виртуальных методов.
Конструкторов в объекте может быть сколько угодно, один из конструкторов обязательно должен быть вызван перед вызовом первого виртуального метода (иначе программа попросту "зависнет"), и конструктор сам не может быть виртуальным.
Что касается другого "хитрого" метода - деструктора, то он не имеет никакого отношения к виртуализации методов, и зачем он нужен, мы расскажем позже. Отметим только, что деструктор в отличие от конструктора вполне может быть виртуальным
