Dispose(pCircle, Done);
Вызов деструктора без Dispose не приводит к автоматическому высвобождению памяти, и он работает как обычный метод. Надо отметить, что идеология работы с конструкторами и деструкторами в Turbo Pascal является запутанной и непродуманной. Эти недостатки ликвидированы в Delphi.
Правила присваивания. Совместимость типов для объектов.
Для потомков при использовании их "на месте" прародителей любого уровня имеется два основных варианта совместимости типов (по вызовам и по присваиваниям) между:
экземплярами объектов, указателями на экземпляры объектов, формальными и фактическими параметрами.Потомки могут использоваться вместо прародителей (но не наоборот!) по следующей причине: используемый тип должен для совместимости полностью "заполнять" поля типа, вместо которого он подставляется.
Например:
aDot:=aCircle;
aDot:=aArc;
{aCircle:=aDot - так нельзя!}
При этом в наш объект, которому присваивается значение, копируются только те поля данных, которые имеются в Dot, а остальные оказываются ненужными и игнорируются. Если бы мы попытались сделать присваивания в обратную сторону (aCircle:=aDot или aArc:=aDot), часть полей результата не смогла бы заполниться. Поэтому присваивания типа "потомок:=прародитель" недопустимы. Стоит отметить, что описанные выше разрешенные присваивания объектов как таковых в последующих версиях PASCAL невозможны, и работа с объектами идет только через указатели (смотри далее раздел про Delphi).
Для указателей присваивание pDot:=pCircle допустимо, а pCircle:=pDot недопустимо, т. е. указателю на прародителя можно присвоить указатель на потомка, но не наоборот. Это правило логически вытекает из описанного выше правила совместимости для объектов. Оно сохранено в Delphi, хотя его основа — правило присваивания объектов — в Delphi ликвидировано. Поэтому изучение объектной модели Turbo Pascal полезно для правильного понимания объектной модели Delphi.
В TurboPascal имеется предопределенный тип Pointer, совместимый по присваиванию для всех типов указателей, то есть переменной типа Pointer можно присвоить указатель любого типа. Но при дальнейшем ее использовании следить за работой с указателями на тип должен сам программист. Обратное присваивание (указателю на объект указателя типа Pointer) возможно только с помощью явного указания приведения типов.
Особенностью рассмотренных правил совместимости является то, что при работе с указателями на полиморфные объекты (т. е. при наличии в объектах виртуальных методов) бывает заранее неизвестно, какой тип вызовется. Вызов:
pDot^.Show
может показать и точку, и круг, и дугу (и даже еще неизвестного нам потомка) в зависимости от того, указатель на какую фигуру был присвоен указателю pDot. Это может зависеть от различных условий. Например, от решения пользователя в программе. В приводимом ниже примере указателю типа tpDot намеренно дано название не pDot, а pFigure, чтобы подчеркнуть, что он может указывать на произвольную фигуру — потомок tDot:
var FigKey:Char;
pFigure:tpDot;
begin
...
outTextXY('Choose type of a figure:0-dot,1-circle,2-arc',0,10);
readln(FigKey);
case FigKey of
'0':pFigure:=pDot;
'1':pFigure:=pCircle;
'2':pFigure:=pArc;
end;
...
pFigure^.Show; {не забыть, что до этого экземпляр должен был быть
инициализирован конструктором!}
...
end;
Выбор конкретного виртуального метода для объекта pFigure^ происходит во время выполнения программы.
Пример на создание, присваивание и уничтожение динамических объектов.
Unit FigDemo1;
Uses…,Figures, FigArc;
var FigKey:char;
Ang1_ini, Ang2_ini, R_ini, X_ini, Y_ini:Integer; newX, newY, newR:Integer;
pFigure:tpDot;
outside:Boolean;
begin
OutTextXY('type of figure:0-dot,1-circle,2-arc,0,10);
Readln(FigKey);
OutTextXY('Coordinate X=',0,20);Readln(X_ini);
OutTextXY('Coordinate Y=',0,30);Readln(Y_ini);
case FigKey of
'0':pFigure:=New(tpDot, Init(X_ini, Y_ini));
'1':begin
OutTextXY('Radius R=',0,40);Readln(R_ini);
pFigure:=New(tpCircle, Init(X_ini, Y_ini, R_ini));
end;
else
begin
OutTextXY('Radius R=',0,40);Readln(R_ini);
OutTextXY('Start angle=',0,50);Readln(Ang1_ini);
OutTextXY('End angle=',0,60);Readln(Ang2_ini);
pFigure:=New(tpArc, Init(X_ini, Y_ini, R_ini, ang1_ini, ang2_ini));
end;
end{/case};
pFigure^.Show;
repeat
OutTextXY('New X=',0,90);readln(NewX);
OutTextXY('New Y=',0,80);readln(NewY);
OutSide:=(NewX<0)OR(NewX>639)OR(NewY<100)OR(NewY>479));
pFigure^.MoveTo(NewX, NewY)
until Outside;
OutTextXY('End of work. Press <Enter>',0,90);Readln;
Dispose(pFigure, Done)
end.
Пример динамического выделения и высвобождения памяти для однонаправленного списка объектов.
Создадим список, в котором хранятся различные фигуры — объекты иерархии с классом-прародителем tDot. Введем запись типа "узел", ссылающийся на одну фигуру и один другой (предыдущий) узел:
Тело списка (или для простоты просто список) будет состоять из набора таких узлов. Самый первый узел, которому не на кого указывать как на предыдущий, имеет указатель со значением nil. Указатель на последний узел списка хранится в поле LastNode нашего объекта. Этот объект типа tList реально будет являться заголовком списка, где хранятся нужные поля данных (в нашем случае — pLastNode) и ссылки на методы объекта. Сам же список будет создаваться в динамической области памяти. К списку можно добавлять новый узел методом Add, а уничтожать методом Done. Метод Report позволяет в процессе работы выводить информацию о координатах фигур, находящихся в списке.
unit ListDemo;
uses…,Figures, FigArc;
type
tpFigure=tpDot;
tpNode=^tNode;{для указателей разрешен опережающий вызов описания типа}
tNode= {хотя тип tNode определен только в этом месте}
record {каждый узел списка состоит из:}
pFigure:tpFigure; {-указателя на фигуру}
pPrevious:tpNode; {-указателя на предыдущий узел списка}
end;
tList=
object {объект этого типа будет управлять списком}
pLastNode:tpNode; {указатель на последний узел списка}
constructor Init; {инициализация полей и методов "управляющего"
объекта}
destructor Done;virtual;{уничтожение списка и высвобождение памяти}
procedure Add(pFig:tpFigure); {добавить узел со ссылкой на
pFig в конец списка}
procedure Report; {вывод на экран информации о списке}
end;
реализация методов tList
constructor tList. Init;
begin
pLastNode:=nil; {в списке узлов нет, и pLastNode никуда не указывает}
end;
procedure tList. Add(pFig:tpFigure); {в качестве параметра передается
указатель на фигуру, которую надо внести в список}
var pCurrent:tpNode; {локальная переменная типа 'указатель на узел'}
begin
New(pCurrent); {создали новый узел, он пока не в списке}
pCurrent^.pFigure:=pFig; {установили его указатель pFigure на фигуру,
на которую "настроен" pFig}
pCurrent^.pPrevious:=pLastNode; {установили его указатель pPrevious
на последний узел списка; если это первый узел списка,
то pPrevious получает значение nil - см. конструктор}
pLastNode:=pCurrent; {обозначили узел как последний в списке;
теперь наш узел состоит в списке}
end;
destructor tList. Done;
var pCurrent:tpNode;
begin
while pLastNode<>nil do {движемся от последнего узла списка до начала,
т. е. пока pLastNode не укажет на nil}
begin
pCurrent:=pLastNode; {настраиваем pCurrent на последний узел}
Dispose(pCurrent^.pFigure, Done);{высвобождаем память, занятую под
фигуру. Важно, что ее метод Done - виртуальный}
pLastNode:= pCurrent^.pPrevious; {обозначаем предыдущий узел как
последний}
Dispose(pCurrent); {устраняем узел, на который настроен указатель
pCurrent}
end;
end;
procedure tList. Report; {вывод на экран координат фигур списка}
var pCurrent:tpNode;
tmp:string;
begin
pCurrent:=pLastNode; {настроились на последний узел списка}
while pCurrent<>nil do {движемся по списку до первого узла}
begin
Str(pCurrent^.pFigure^.GetX:3,tmp); {в строку tmp вводим 3 цифры
значения X}
OutText('X='+tmp+' '); {выводим значение X на экран}
Str(pCurrent^.pFigure^.GetY:3,tmp); {в строку tmp – значениеY}
OutText('Y='+tmp+' '); {выводим значение Y на экран}
pCurrent:=pCurrent^.pPrevious; {настроились на предыдущий узел}
end;
end;
переменные основной программы
var
aList:tList; {экземпляр типа "список"}
pArc:tpArc; {указатель на дугу}
pCircle:tpCircle; {указатель на окружность}
основная программа
begin
InitGraph(GraphDriver, GraphMode,''); {инициализация графики}
if GraphResult<>GrOk then Halt(1); {в случае ошибки - остановка
программы}
aList. Init; {создали список. В нем нет узлов, а поле pLastNode равно nil}
aList. Add(New(tpArc, Init(120,80,20,10,90))); {добавили в список
дугу}
aList. Add(New(tpCircle, Init(300,200,100))); {добавили окружность}
aList. Add(New(tpCircle, Init(150,150,50))); {другую окружность}
aList. pLastNode^.pFigure^.Show; {показали последнюю фигуру списка}
aList. pLastNode^.pFigure^.MoveTo(200,200); {передвинули ее}
aList. Report; {вывели на экран координаты фигур списка}
aList. Add(New(tpDot, Init(100,100))); {добавили в список точку}
aList. pLastNode^.pPrevious^.pPrevious^.pFigure^.Show; {показали
третью с конца фигуру, т. е. первую окружность}
aList. pLastNode^.pFigure^.Move(20,50); {передвинули точку}
aList. Report; {вывели координаты фигур списка}
aList. Done; {уничтожили список}
OutTextXY('Press <Enter> to exit',0,10);
end.
Очередь, стек, двунаправленный список.
Совершенно аналогичным образом можно создавать объект — двунаправленный список, в котором каждый узел имеет указатели как "влево" (на предыдущий узел), так и "вправо" (на последующий). В таком объекте полезно иметь поля:
pLastNode — указатель на последний узел списка, pFirstNode — указатель на первый элемент. nNodes — число имеющихся на данный момент узлов (в Delphi аналогичное поля обычно носит имя Count), pCurrent — указатель на текущий узел списка (с которым мы в данный момент работаем), nCurrent — номер текущего узла.Кроме того, надо предусмотреть методы:
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |


