Объективно-ориентированная технология программирования в языке Паскаль

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

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык русский
Дата добавления 17.02.2012
Размер файла 109,9 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

В основу механизма реализации позднего связывания может быть положена абстракция действий через параметризацию. Действительно, описание метода, достаточное для его компиляции, может строиться на указании формального параметра-переменной, который в процессе выполнения может заменяться фактическим параметром - любым из производных типов, полученных от типа фактического параметра (см. совместимость типов объектов). Здесь можно отметить, что в Borland Pascal этот механизм реализуется посредством специальной таблицы VMT, определяющей точки вызова требуемых виртуальных методов.

Метод определяется как виртуальный, когда за его описанием в типе объекта ставится зарезервированное слово virtual.

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

Описание типов рассмотренных выше графических объектов с использованием виртуальных методов сводится к следующему:

type

Location = object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

Point = object(Location)

Visible : Boolean;

constructor Init(InitX, InitY : Integer);

procedure Show; virtual;

procedure Hide; virtual;

function IsVisible : Boolean;

procedure MoveTo(NewX, NewY : Integer);

end;

Circle = object(Point)

Radius : Integer;

constructor Init(InitX, InitY : Integer; InitRadius : Integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Expand(ExpandBy : Integer); virtual;

procedure Contract(ContractBy : Integer); virtual;

end;

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

Кроме того, следует отметить использование нового служебного слова constructor (конструктор), которое заменило зарезервированное слово procedure для Point.Init и Circle.Init при описании метода инициализации в списке методов полиморфных объектов. Традиционное имя метода инициализации (Init) по-прежнему сохраняется. Все типы объектов, которые имеют виртуальные методы, должны иметь конструктор. Конструктор - это особый вид процедуры, которая выполняет некоторую установочную работу для механизма реализации виртуальных методов. Более того, конструктор должен вызываться до вызова виртуального метода. Вызов виртуального метода без предварительного вызова конструктора может вызвать тупиковое состояние (компилятор не сможет проверить порядок вызова методов). При этом каждый отдельный экземпляр объекта должен быть инициализирован отдельным вызовом конструктора. В этом случае недостаточно инициализировать один экземпляр объекта и затем присвоить этот экземпляр дополнительным экземплярам. Дополнительные экземпляры не будут инициализированы оператором присваивания, и, при вызове их виртуальных методов, возникнет ошибка времени выполнения.

Необходимость конструкторов обусловлена тем, что для вызова виртуальных методов применительно к каждому экземпляру объекта используется так называемая таблица виртуальных правил - VMT (Virtual Metods Table). Эта таблица содержит размер типа объекта, и, для каждого из его виртуальных методов, указатель на точку вызова соответствующего кода (Рис.8.2.). Конструктор же устанавливает связь между экземпляром, вызывающим этот конструктор, и таблицей виртуальных правил данного типа объекта.

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

Вызовы виртуальных методов желательно проверять на статус инициализации того экземпляра, который делает вызов. Осуществить такую проверку позволяет директива транслятора ($R+). Вызов экземпляра, не инициализированного своим конструктором, будет отмечен сообщением об ошибке (положение переключателя по умолчанию - неактивное или $R-). Если есть уверенность в том, что в программе отсутствуют вызовы методов неинициализированными экземплярами объектов, желательно не активизировать директиву, поскольку проверка существенно “замедлит” работу программы.

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

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

Aano?aeoey Figures может быть сформулирована на основе общих свойств всех возможных фигур и их отличительных особенностей. Различия очевидны: это формы (линии, углы и кривые, рисуемые на экране). Общие свойства всех фигур определяются следующими атрибутами:

Эти атрибуты в точности соответствуют характеристикам типов объектов Location и Point. Объект Point представляет собой, в сущности, абстрактный тип объекта, от которого все объекты графических фигур являются производными. В рассматриваемом случае никакой экземпляр объекта типа Point не потребуется “рисовать” на экране, хотя вызов Point.Show позволяет “показать” точку. Поэтому тип объекта, специально созданный для порождения потомков наследуемыми свойствами, собственно и называется абстрактным типом.

Aano?aeoiue объект, кроме того, позволяет при необходимости дополнить все порожденные объекты новым общим для них свойством не затрагивая при этом описания потомков. Так, например, необходимость поддержки цвета применительно ко всем типам объектов, производных от объекта Point, потребует изменений только в описании типа Point. Методы для передвижения любой фигуры в текущую позицию указателя "мыши" также не потребуются. Для этого достаточно дополнить соответствующим методом описание Point, так как этот метод будет влиять только на два поля - X и Y.

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

В рассматриваемой задаче наибольший интерес представляет буксировка произвольной графической фигуры по экрану в ответ на входные данные пользователя. Iaeaieaa i?aaeaiue nпособ решения такой задачи, который связан с традиционной процедурной технологией, заключается в том, что модуль Figures будет экспортировать процедуру, которой в качестве параметра переменной передается полиморфный объект, буксируемый по экрану. Такая процедура может иметь вид:

procedure DragIt(var AnyFigure : Point; DragBy : Integer);

var

DeltaX,DeltaY : Integer;

FigureX,FigureY: Integer;

begin

AnyFigure.Show; {показывает фигуру,которая буксируется}

FigureX := AnyFigure.GetX; {читает начальные значения }

FigureY := AnyFigure.GetY; {координат X,Y буксируемой фигуры}

while GetDelta(DeltaX, DeltaY) do {цикл буксировки}

begin

FigureX := FigureX + (DeltaX * DragBy);

FigureY := FigureY + (DeltaY * DragBy);

AnyFigure.MoveTo(FigureX, FigureY)

end

end;

Процедура DragIt вызывает дополнительную процедуру GetDelta, которая в примере не описана. Предполагается, что GetDelta получает приращения координат DeltaX, DeltaY от пользователя. Они могут вводиться с клавиатуры, посредством "мыши", или с помощью другого устройства.

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

Методы GetX, GetY и MoveTo являются статическими (это означает, что во время компиляции DragIt известны точки вызова процедуры, реализующей каждый метод). С другой стороны, метод Show, как уже упоминалось, должен быть виртуальным. В этом случае (при вызове DragIt) точка вызова требуемой реализации Show определяется в VMT экземпляром, передаваемым в AnyFigure как параметр. Таким образом DragIt сможет "буксировать" любой производный от Point тип по экрану, вне зависимости от того, известен этот тип во время компиляции модуля, или нет.

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

Прежде чем дополнять существующую иерархию объектов новым методом, всегда необходимо определить, как высоко в этой иерархии он должен быть помещен. Буксировка фигуры влечет за собой изменения в местонахождении фигуры в ответ на входные данные, вводимые пользователем. Тогда, с учетом наследования, метод Drag должен находится “рядом” с MoveTo, поскольку любой объект, для которого приемлем метод MoveTo, должен также наследовать Drag. Иными словами, Drag может быть включен в список методов абстрактного объекта Point.

Далее следует определить, каким будет метод - статическим или виртуальным. Критерием “виртуализации” любого метода служит вероятность изменения метода с учетом всего дерева порождения. Метод Drag манипулирует только позицией фигуры. Следовательно, необходимости делать его виртуальным, по-видимому, нет.

Iaiaei i?eiятие подобных решений требует осторожности: статический метод Drag заблокирует все возможности для пользователей модуля Figures изменять этот метод при расширении модуля. К примеру, метод Drag может изменятся в связи с обработкой событий, т. е. "перехватом" входных данных либо от клавиатуры, либо от "мыши". Событие происходит непредсказуемо по времени. а обработка таких данных должна производится по их получении. Кроме того, обработка событий часто зависит от аппаратного обеспечения, а пользователю может понадобиться новое устройство для ввода, которое не согласуется с имеющейся "версией" Drag, и пользователь будет бессилен переписать этот метод.

Iiyoiio iaoia Drag все же следует определять как виртуальный. Процесс преобразования процедуры DragIt в метод достаточно прост и заключается в пополнении списка методов объекта Point заголовком соответствующей процедуры (в рассматриваемом случае - DragBy):

Point = object(Location)

Visible : Boolean;

constructor Init(InitX, InitY : Integer);

procedure Show; virtual;

procedure Hide; virtual;

function IsVisible : Boolean;

procedure MoveTo(NewX, NewY : Integer);

procedure DragBy(DragBy : Integer); virtual;

end;

самой процедуры DradIt также потребует изменений. Теперь правило Drag является частью объекта Point. Поэтому при ее формулировке не требуется уточнять имена методов в связи с формальным параметром AnyFigure и, естественно, использовать сам параметр. “Заботу” о том, какой экземпляр объекта вызывает правило Drag, возьмет на себя неявный параметр Self (cформируется ссылка на соответствующую типу экземпляра VMT).

исходный текст модуля FIGURES может быть оформлен следующим образом.

unit Figures;

interface

uses Graph, Crt;

type

Location = object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

PointPtr = ^Point;

Point = object(Location)

Visible : Boolean;

constructor Init(InitX, InitY : Integer);

destructor Done; virtual;

procedure Show; virtual;

procedure Hide; virtual;

function IsVisible : Boolean;

procedure MoveTo(NewX, NewY : Integer);

procedure Drag(DragBy : Integer); virtual;

end;

CirclePtr = ^Circle;

Circle = object(Point)

Radius : Integer;

constructor Init(InitX, InitY : Integer; InitRadius : Integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Expand(ExpandBy : Integer); virtual;

procedure Contract(ContractBy : Integer); virtual;

end;

implementation

{Реализация методов объекта Location}

procedure Location.Init(InitX, InitY : Integer);

begin

X :=InitX;

Y :=InitY;

end;

function Location.GetX : Integer;

begin

GetX := X;

end;

function Location.GetY : Integer;

begin

GetY := Y;

end;

{Реализация методов объекта Points}

constructor Point.Init(InitX, InitY : Integer);

begin

Location.Init(InitX, InitY);

Visible := False;

end;

destructor Point.Done; {назначение деструкторов поясняется позднее}

begin

Hide;

end;

procedure Point.Show;

begin

Visible :=True;

PutPixel(X, Y, GetColor);

end;

procedure Point.Hide;

begin

Visible :=False;

PutPixel(X, Y, GetBkColor);

end;

function Point.IsVisible : Boolean;

begin

IsVisible := Visible;

end;

procedure Point.MoveTo(NewX, NewY : Integer);

begin

Hide;

X :=NewX;

Y :=NewY;

Show;

end;

function GetDelta(var DeltaX,DeltaY : Integer) : Integer;

var

KeyChar : Char;

Quit : Boolean;

begin

DeltaX :=0;

DeltaY :=0; {0 - нет изменений в положении}

GetDelta :=True; {True - возвращает дельту}

repeat KeyChar := ReadKey; {cчитывание нажатой клавиши}

Quit :=True; {используемая клавиша}

case Ord(KeyChar) of

0: begin {“0” означает расширенный 2-байтовый код}

KeyChar := ReadKey; {читать второй байт кода}

case Ord(KeyChar) of

72: DeltaY :=-1; {стрелка вверх; декремент Y}

80: DeltaY :=1; {стрелка вниз; инкремент Y}

75: DeltaX :=-1; {стрелка влево; декремент X}

77: DeltaX :=1; {стрелка вправо; инкремент X}

else Quit :=False; {игнорирует любой другой код}

end; {case}

end;

13: GetDelta :=False; {нажатие CR означает “не дельта”}

else Quit :=False; {игнорирует все другие нажатия клавиш}

end; {case}

until Quit;

end;

procedure Point.Drag(DragBy : Integer);

var

DeltaX, DeltaY : Integer;

FigureX, FigureY : Integer;

begin

Show; {показ фигуры, которая будет буксироваться}

FigureX := GetX; {возврат начального положения фигуры}

FigureY := GetY;

while GetDelta(DeltaX, DeltaY) do {цикл буксировки}

begin { Применяет дельту к фигуре X,Y: }

FigureX :=FigureX + (DeltaX * DragBy);

FigureY :=FigureY + (DeltaY * DragBy);

MoveTo(FigureX, FigureY); {перемещение фигуры }

end;

end;

{ Реализация методов бъекта Circle}

constructor Circle.Init(InitX, InitY : Integer; InitRadius : Integer);

begin

Point.Init(InitX, InitY);

Radius :=InitRadius;

end;

procedure Circle.Show;

begin

Visible :=True;

Graph.Circle(X, Y, Radius);

end;

procedure Circle.Hide;

var

TempColor : Word;

begin

TempColor :=Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible := False;

Graph.Circle(X, Y, Radius);

Graph.SetColor(TempColor);

end;

procedure Circle.Expand(ExpandBy : Integer);

begin

Hide;

Radius :=Radius + ExpandBy;

if Radius <0 then Radius :=0;

Show;

end;

procedure Circle.Contract(ContractBy : Integer);

begin

Expand(-ContractBy);

end;

{раздел инициализации}

begin

end.

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

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

Расширяемость, собственно, обусловлена наследованием и полиморфизмом (позднее связывание позволяет “учитывать дополнения” во время выполнения программы, так что расширение существующего модуля никак не связано с его изменением).

Следующая программа использует модуль Figures, и расширяет его, дополняя новым объектом графической фигуры Arc (дуга). Arc является производным типом от объекта Circle и наследует методы MoveTo или Drag без каких-либо особых изменений. Позднее связывание позволяют методу Drag вызывать виртуальные методы Show и Hide объекта Arc вне зависимости от того, когда был откомпилирован Point.Drag:

program FigureDemo; {расширение Figures типом Arc}

uses Crt, DOS, Graph, Figures;

type

Arc = object(Circle)

StartAngle, EndAngle : Integer;

constructor Init (InitX, InitY: Integer; InitRadius: Integer;

InitStartAngle, InitEndAngle : Integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

var

GraphDriver : Integer;

GraphMode : Integer;

ErrorCode : Integer;

AnArc : Arc;

ACircle : Circle;

{Определение методов объекта Arc}

constructor Arc.Init(InitX, InitY : Integer; InitRadius : Integer;

InitStartAngle, InitEndAnle : Integer);

begin

Circle.Init(InitX, InitY, InitRadius);

StartAngle :=InitStartAngle;

EndAngle :=InitEndAngle;

end;

procedure Arc.Show;

begin

Visible :=True;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius);

end;

procedure Arc.Hide;

var

TempColor : Word;

begin

TempColor :=Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible :=False;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius); {pисует дугу в цвете

заднего фона для ее скрытия}

SetColor(TempColor);

end;

{Тело программы}

begin

GraphDriver := Detect; {определение типа монитора}

InitGraph(GraphDriver, GraphMode,'');

if GraphResult <> GrOK then

begin

WriteLn('Halted on graphics error:');

Halt(1)

end;

{Все потомки от типа Point содержат виртуальные правила и должны

быть инициализированы вызовом конструктора}

ACircle.Init(151, 82, 50);

AnArc.Init(151, 82, 25, 0, 90);

{AnArc заменяется экземпляром AnCircle для буксировки круга вместо дуги.

Нажать ENTER для остановки буксировки и завершения программы}

AnArc.Drag(5);

CloseGraph;

end.

Свойство расширяемости является основой для создания мощных библиотек, которые сами могут рассматриваться как дополнение инструментария языка. Примерами таких библиотек являются Turbo Vision или, в последних версиях, Pascal Object Library. Однако, свойство расширяемости вступает в некоторые противоречия с эффективностью программ, написаных с применением ООП-технологии. Эти противоречия вызваны необходимостью выбора раннего или позднего связывания при описании методов. В общем, предпочтение следует отдавать виртуальным методам. Статические методы можно использовать в тех случаях, когда требуется достичь большей скорости и эффективности использования памяти. Справедливости ради, следует заметить, что выигрыш здесь будет небольшим (потери при позднем связывании определяются необходимостью “обработки” VMT), а дополнительная скорость и эффективность использования памяти, которую дают статические правила, компенсируются гибкостью полиморфных объектов.

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

Все примеры объектов, рассматриваемые до сих пор, имели статические экземпляры (термин "статический"здесь имеет смысл в контексте понятия “переменная” и не имеет никакого отношения к статическим правилам).

Экземпляры объктного типа, подобно переменным, могут размещаться в динамической области (heap). В этом случае принято говорить о динамических объектах. Кроме того, объектный тип можно использовать в качестве компонент динамических (рекурсивных) структур данных, если среди полей данных предусмотрено поле, которое является ссылкой на этот же тип объекта.

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

type

Location =object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

PointPtr =^Point;

Point =object(Location);

var

PPoint : PointPtr;

begin

new (PPoint);

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

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

ХPosition :=PPoint ^.GetX;

Однако, синтаксис процедуры new применительно к динамическим объектам в Borland Pascal существенно расширен (в том числе и по отношению к расширениям, описанным в разделе 7), что позволяет выделять полиморфному объекту требуемое место в динамической области и инициализировать его с помощью одной операции. В расширенной трактовке процедура new может быть вызвана с двумя параметрами: именем указателя в качестве первого параметра и вызовом конструктора в качестве второго. Наример, распределение памяти для объекта Circle с последующей его инициализацией выполняется с помощью вызова:

new(PCircle,Init(600, 100, 30));

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

Более того, процедура new в расширенной трактовке может использоваться как функция, возвращающая значения указателя, т. е. возможны присваивания вида:

type

ArcPtr =^Arc;

var

PArc : ArcPtr;

begin

PArc :=New(ArcPtr);

или

PArc :=new(ArcPtr, Init(600, 100, 25, 0, 90));

Для очистки памяти, которая была распределена под “использованные” динамические объекты, традиционно применяется процедура Dispose. Обычно вызов процедуры Dispose осуществляется оператором вида Dispose(PMyObject), где PMyObject - указатель на использованный динамический объект.

Однако, подобный вызов применим только к объектам, методы которого предполагают раннее связывание (описаны как статические). Для полиморфных объектов освобождаемый объем памяти не является постоянной величиной и определяется с помощью одной из строк таблицы VMT (см. Рис.8.2.)

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

Более того, полиморфный объект может содержать указатели на динамические структуры или объекты, которые необходимо освободить или "стереть" в определенном порядке, особенно в случае, когда объект оперирует с другими сложными структурами данных. Все, что необходимо для стирания динамического объекта, должно быть собрано вместе, в одном методе, чтобы объект мог быть устранен одним его вызовом. Для таких методов обычно используется имя Done. Иными словами, метод Done должен инкапсулировать все детали стирания объекта и все структуры данных и объекты, вложенные в него. Допустимо и иногда удобно определять несколько методов, определяющих способы стирания данного типа объекта в зависимости от того, как они размещаются и используются, или же в зависимости от того, в каком состоянии находится объект, когда осуществляется его уничтожение из динамической памяти.

Подобно конструкторам в Borland Pascal для этого случая предусмотрен особый вид методов, которые называются деструкторами. Деструктор описывается в списке методов типа объекта с помощью зарезервированного слова destructor, например:

Point =object(Location)

Visible : Boolean;

constructor Init(InitX, InitY : Integer);

destructor Done; virtual;

end;

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

Зарезервированное слово destructor не является необходимым для каждого метода очистки, даже если определение типа объекта содержит виртуальные правила. Фактически деструкторы “работают” только с динамически распределяемыми объектами. Но описание деструкторов в списке методов для статически распределенных объектов вреда не принесет, а отсутствие деструкторов исключает возможность использовать такие объекты в качестве динамических.

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

Dispose(PPoint,Done);

При этом деструктор определяет размер освобождаемой памяти в VMT данного типа экземпляра и передает этот размер процедуре Dispose. Процедура завершает очистку, освобождая соответствующую память. Таким образом, вызов деструктора вне процедуры Dispose не выполнит автоматической очистки памяти.

Число освобождаемых байт будет верным вне зависимости от того, указывает ли PPoint на экземпляр типа Point, или же на один из производных от Point типов, например, Circle или Arc.

Тело процедуры, описывающей деструктор, может быть пустым, т. е.:

destructor MyObject.Done;

begin

end;

и все-таки деструктор будет выполнять свою задачу. В таком деструкторе существенно не тело правила, а код, сгенерированный компилятором в ответ на служебное слово destructor.

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

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

Решение, которое не требует внесения никаких изменений в описание графических объектов, состоит в создании нового типа объекта, не являющегося производным от объекта Point. Тип List (список) - простой объект, назначение которого состоит в определении указателя на голову списка объектов типа Point. В качестве компонент списка теперь можно использовать обычную запись, например, Node (узел), полями которой будут указатель на тип Point и ссылка Next:

NodePtr =^Node;

Node =record

Item : PointPtr;

Next : NodePtr

end;

Описание типа List имеет вид:

List = object

Nodes: NodePtr;

constructor Init;

destructor Done; virtual;

procedure Add(Item : PointPtr);

procedure Report;

end;

Применительно к объекту List интерес в данном примере представляет метод Add и деструктор Done.

Метод Add позволяет формировать список, помещая новую компоненту в его начало. В соответствии с определением совместимости объектных типов и указателей, указатель на любой тип фигуры, производный от Point, также может быть передан List.Add в параметре Item:

procedure List.Add(Item : PointPtr);

var

N : NodePtr;

begin

new(N);

N^.Item :=Item;

N^.Next :=Nodes;

Nodes :=N;

end;

В примере используется статически размещаемый экземпляр объекта типа List - Alist и формируется список с тремя узлами. Каждый узел указывает на отдельную графическую фигуру, которой является либо объект Point, либо один из его производных типов. Затем вся структура, включая три записи Node и три объекта типа Point, “очищается” и убирается из динамической области с помощью одного вызова деструктора AList.Done :

destructor List.Done;

var

N: NodePtr;

begin

while Nodes <> nil do

begin

N :=Nodes;

Dispose(N^.Item, Done);

Nodes :=N^.Next; Dispose(N)

end

end;

Таким образом список очищается начиная с "головы". Вызов dispose освобождает память, использованную для размещения первого элемента списка (Item^); затем Nodes передается следующей компоненте Node в списке оператором Nodes :=N^.Next; компонента снова освобождается и весь процесс повторяется, пока список не будет полностью очищен.

Здесь можно еще раз напомнить, что при вызове деструктора Dispose(N^.Item, Done) фактический тип N^.Item - не обязательно Point, Он также может быть любым, производным тип от типа Point. Удаляемый объект является полиморфным и размер его экземпляра определяется в соответствующей таблице виртуальных правил.

В тексте примера AList является экземпляром статического объекта. Однако, этот объект можно описать как динамический. В этом случае для его размещения в heap-области понадобиться указатель типа ListPtr, а для уничтожения - деструктор PList,Done, вызов которого Dispose(PList,Done) обеспечит очистку памяти от "головы" списка.

Полный текст примера с дополнительными комментариями приведен ниже; в нем используется уже известный модуль Figures, в котором тип Arc описан как производный тип от типа Point:

program prim8_1;

uses Graph, Figures;

type

ArcPtr = ^Arc;

Arc = object(Circle)

StartAngle, EndAngle : Integer;

constructor Init(InitX, InitY : Integer; InitRadius : Integer;

InitStartAngle, InitEndAngle : Integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

NodePtr = ^Node;

Node = record

Item : PointPtr;

Next : NodePtr;

end;

ListPtr = ^List;

List = object

Nodes: NodePtr;

constructor Init;

destructor Done;virtual;

procedure Add(Item : PointPtr);

procedure Report;

end;

var

GraphDriver : Integer;

GraphMode : Integer;

Temp : string;

AList : List;

Parc : ArcPtr;

PCircle : CirclePtr;

RootNode : NodePtr;

{Процедуры, которые не являются правилами}

procedure OutTextLn(TheText : string);

begin

OutText(TheText);

MoveTo(0, GetY + 12)

end;

procedure HeapStatus(StatusMessage : string);

begin

Str(MemAvail : 6, Temp);

OutTextLn(StatusMessage + Temp);

end;

{Реализация методов объекта Arc }

constructor Arc.Init(InitX, InitY : Integer; InitRadius : Integer;

InitStartAngle, InitEndAngle : Integer);

begin

Circle.Init(InitX, InitY, InitRadius);

StartAngle := InitStartAngle;

EndAngle := InitEndAngle;

end;

procedure Arc.Show;

begin

Visible := True;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius);

end;

procedure Arc.Hide;

var

TempColor : Word;

begin

TempColor := Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible := False;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius);

SetColor(TempColor);

end;

{Реализация методов объекта List }

constructor List.Init;

begin

Nodes := nil;

end;

destructor List.Done;

var

N : NodePtr;

begin

while Nodes <> nil do

begin

N := Nodes;

Dispose(N^.Item, Done);

Nodes := N^.Next;

Dispose(N)

end

end;

procedure List.Add(Item : PointPtr);

var

N : NodePtr;

begin

New(N);

N^.Item := Item;

N^.Next := Nodes;

Nodes := N

end;

procedure List.Report;

var

Current : NodePtr;

begin

Current := Nodes;

while Current <> nil do

begin

Str(Current^.Item^.GetX : 3, Temp);

OutTextLn('X = '+Temp);

Str(Current^.Item^.GetY : 3, Temp);

OutTextLn('Y = '+Temp);

Current := Current^.Next

end

end;

{Тело программы}

begin

InitGraph(GraphDriver, GraphMode,'');

if GraphResult <> GrOK then

begin

WriteLn('Остановка при графической ошибке: ',

GraphErrorMsg(GraphDriver));

Halt(1);

end;

HeapStatus ('Пространство в куче до распределения списка: ')

{ Создать список }

AList.Init;

{создание и пополнение списка несколькими фигурами за одну операцию}

AList.Add(New(ArcPtr, Init(151, 82, 25, 200, 330)));

AList.Add(New(CirclePtr, Init(400, 100, 40)));

AList.Add(New(CirclePtr, Init(305, 136, 5)));

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

AList.Report;

HeapStatus ('пространство в “куче” после распределения списка: ');

{весь список освобждается одним вызовом деструктора}

AList.Done;

HeapStatus (`пространство в “куче” после очистки списка:');

OutText('Нажмите ВВОД для завершения программы:`);

ReadLn;

CloseGraph

end.

Размещено на Allbest.ru


Подобные документы

  • Логические конструкции в системе программирования Паскаль. Команды языка программирования, использование функций, процедур. Постановка и решение задач механики в среде системы Паскаль. Задачи статики, кинематики, динамики решаемые с помощью языка Паскаль.

    курсовая работа [290,9 K], добавлен 05.12.2008

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

    лекция [55,7 K], добавлен 21.05.2009

  • Стандартизированный процедурный язык программирования. Создание системного программного обеспечения и прикладных программ. Особенности языка Си, его основные недостатки. Передача параметров в функцию по значению. Стандартная библиотека языка Си.

    презентация [396,3 K], добавлен 12.11.2012

  • Цели и задачи дисциплины "Технология программирования". Программные средства ПК. Состав системы программирования и элементы языка. Введение в систему программирования и операторы языка Си. Организация работы с файлами. Особенности программирования на С++.

    методичка [126,3 K], добавлен 07.12.2011

  • Особенности программирования на языке Паскаль в среде Турбо Паскаль. Линейные алгоритмы, процедуры и функции. Структура данных: массивы, строки, записи. Модульное программирование, прямая и косвенная рекурсия. Бинарный поиск, организация списков.

    отчет по практике [913,8 K], добавлен 21.07.2012

  • История и основы структурного программирования в среде Turbo Pascal. Работа с различными типами данных. Операторы языка. Работа с символьными и строковыми переменами, одномерным, двумерным массивами. Классификация компьютерных игр. Игры на языке Паскаль.

    курсовая работа [28,8 K], добавлен 06.05.2014

  • Сущность понятия "тип данных". Объектно-ориентированный стиль программирования. Простые типы данных в языке Паскаль: порядковые, вещественные, дата-время. Булевский (логический) тип. Синтаксис определения ограниченного типа. Регулярные типы (массивы).

    реферат [24,1 K], добавлен 01.12.2009

  • Совместимость и преобразование типов данных. Создание информационно-поисковой системы на языке программирования Паскаль. Описание интерфейса, каждого блока программы "Картотека больных". Рассмотрение результатов работы программы, сортирования данных.

    курсовая работа [368,9 K], добавлен 18.05.2015

  • Создание процедурно-ориентированной и объектно-ориентированной реализации конкретного приложения и сравнительный анализ их вычислительной эффективности. Определение попадания точки внутрь фигуры. Возможности улучшения характеристик качества приложений.

    курсовая работа [1,5 M], добавлен 04.12.2021

  • Использование объектно-ориентированной методологии при программировании математических процессов. Среда языка программирования Delphi для решения математических задач. Объектно-ориентированные, декларативные и императивные языки программирования.

    дипломная работа [1,8 M], добавлен 14.09.2011

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.