![]() |
Описание указателей на объект:
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 с деструктором):
Dispose(pCircle, Done);
Вызов деструктора без Dispose не приводит к автоматическому высвобождению памяти, и он работает как обычный метод. Надо отметить, что идеология работы с конструкторами и деструкторами в Turbo Pascal является запутанной и непродуманной. Эти недостатки ликвидированы в Delphi.
Правила присваивания. Совместимость типов для объектов.
Для потомков при использовании их "на месте" прародителей любого уровня имеется два основных варианта совместимости типов (по вызовам и по присваиваниям) между:
1. экземплярами объектов,
2. указателями на экземпляры объектов,
3. формальными и фактическими параметрами.
Потомки могут использоваться вместо прародителей (но не наоборот!) по следующей причине: используемый тип должен для совместимости полностью "заполнять" поля типа, вместо которого он подставляется.
Например:
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 позволяет в процессе работы выводить информацию о координатах фигур, находящихся в списке.
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |



