Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Программирование на языке высокого уровня Паскаль. Учебное пособие для СПО
.pdf
Заголовки всех одноименных виртуальных методов должны
совпадать (параметры должны иметь одинаковый тип, количество
и порядок следования, функции должны возвращать значение
одного и того же типа).
Переопределять виртуальный метод в каждом из потомков не
обязательно: если он выполняет устраивающие потомка действия,
он будет унаследован.
Объект, имеющий хотя бы один виртуальный метод, должен
содержать конструктор.
Для иллюстрации работы виртуальных методов используем программу
из предыдущей лекции, сменив в ней тип элементов массива с
monster на daemon ( пример 7.2). Предварительно в модуле
monsters методы attack, draw и erase объявим как
виртуальные, а в процедурах init заменим ключевое слово
procedure на слово constructor.
program game_2;
uses graph, crt, monsters;
const n = 30;
var stado : array [1 .. n] of daemon;
x, y : array [1 .. n] of integer;
gd, gm : integer;
i, j : word;
begin
gd := detect; initgraph(gd, gm, '...');
if graphresult <> grOk then begin
writeln('ошибка инициализации графики'); exit end;
randomize;
for i := 1 to n do begin
stado[i].init(random(600), random(440), random(10), random(8), random(6));
stado[i].draw;
end;
repeat
for i := 1 to n do begin
x[i] := random(600); y[i] := random(440);
stado[i].move(x[i], y[i]);
end;
for i := 1 to n – 1 do
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
141

for j := i + 1 to n do
if (abs(x[i] – x[j]) < 15) and (abs(y[i] – y[j]) < 15)
then begin
stado[i].hit; stado[j].hit;
end;
delay(200);
until keypressed;
end.
Листинг 7.2. Пример использования виртуальных методов
Единственное изменение, которое пришлось сделать в части
исполняемых операторов программы, — добавление еще одного
параметра в метод инициализации init. Запустив программу, можно
наблюдать процесс самоуничтожения демонов, что свидетельствует о
том, что теперь из методов move и hit, унаследованных из базового
класса, вызываются методы attack, draw и erase, определенные в
производном классе.
Виртуальные методы незаменимы и при передаче объектов в
подпрограммы. В заголовке подпрограммы описывается либо объект
базового типа, передаваемый по адресу, либо указатель на этот объект, а
при вызове в нее передается объект или указатель производного класса.
В этом случае виртуальные методы, вызываемые для объекта из
подпрограммы, будут соответствовать типу аргумента, а не параметра.
При описании классов рекомендуется определять как виртуальные те
методы, которые в производных классах будут реализовываться подругому. Если во всех классах иерархии метод будет выполняться
одинаково или если в потомках он не потребуется, его лучше
определить как статический (обычный). Применение виртуальных
методов обеспечивает гибкость и возможность расширения
функциональности модуля, но несколько замедляет выполнение
программы, поскольку эти методы вызываются через обращение к VMT,
а не непосредственно.
Объекты в динамической памяти
Для хранения объектов в программах чаще всего используется
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
142

динамическая память, поскольку это обеспечивает гибкость программы
и эффективное использование памяти. Благодаря расширенной
совместимости типов можно описать указатель на базовый класс и
хранить в нем ссылку на любой его объект-потомок, что в сочетании с
виртуальными методами позволяет единообразно работать с
различными классами иерархии. Из объектов или указателей на объекты
создают различные динамические структуры.
Для выделения памяти под объекты используются процедура и функция
new. Например, если определены указатели:
type pmonster = ^monster;
pdaemon = ^daemon;
var pm : pmonster;
pd : pdaemon;
можно создать объекты с помощью вызовов:
new(pm); { или pm := new(pmonster); }
new(pd); { или pd := new(pdaemon); }
При использовании new в форме процедуры параметром является
указатель, а в функцию передается его тип. Так как после выделения
памяти объект обычно инициализируют, для удобства определены
расширенные формы new с двумя параметрами. На месте второго
параметра задается вызов конструктора объекта.
new(pm, init(1, 1, 1, 1); { или pm := new(pmonster, init(1, 1, 1, 1)); }
new(pd, init(1, 1, 1, 1, 1); { или pd := new(pdaemon, init(1, 1, 1, 1, 1)); }
Обращение к методам динамического объекта выполняется по обычным
правилам Паскаля, например:
pm^.draw; pm^.attack;
С объектами в динамической памяти часто работают через указатели на
базовый класс, то есть описывают указатель базового класса, а
инициализируют его, создав объект производного класса, например:
pm := new(pdaemon, init(1, 1, 1, 1, 1));
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
143

Как уже говорилось, такие объекты называют полиморфными. Они
используются для того, чтобы можно было единообразно работать в
программе с объектами разных классов. Например, оператор
pm^.draw будет автоматически вызывать разные методы в
зависимости от того, на объект какого типа в данный момент ссылается
указатель pm (это справедливо только для виртуальных методов).
Для освобождения памяти, занятой объектом, применяется процедура
Dispose:
Dispose(pm);
При выполнении этой процедуры освобождается количество байтов,
равное размеру объекта, соответствующего типу указателя.
Следовательно, если на самом деле в указателе хранится ссылка на
объект производного класса, который, как известно, может быть больше
своего предка, часть памяти не будет помечена как свободная, но доступ
к ней будет невозможен, то есть появится мусор (рис. 7.3). Второй
случай появления мусора возникает при применении процедуры
Dispose к объекту, поля которого являются указателями (рис. 7.4).
Объект, содержащий динамические поля, мы рассмотрим в конце этой
лекции.
Рис. 7.3. Неверное удаление полиморфного объекта
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
144

Рис. 7.4. Неверное удаление объекта с динамическими полями
Для корректного освобождения памяти из-под полиморфных объектов
следует использовать вместе с процедурой Dispose специальный
метод — деструктор. В документации по Borland Pascal ему
рекомендуется давать имя done, например:
destructor monster.done;
begin
end;
Для правильного освобождения памяти деструктор записывается
вторым параметром процедуры Dispose.
Dispose(pm, done);
Для простых объектов деструктор может быть пустым, а для объектов,
содержащих динамические поля, в нем записываются операторы
освобождения памяти для этих полей. В деструкторе можно описывать
любые действия, необходимые для конкретного объекта, например
закрытие файлов. Исполняемый код деструктора никогда не бывает
пустым, потому что компилятор по служебному слову destructor
вставляет в конец тела метода операторы получения размера объекта из
VMT. Деструктор передает этот размер процедуре Dispose, и она
освобождает количество памяти, соответствующее фактическому типу
объекта.
ВНИМАНИЕ Вызов деструктора вне процедуры Dispose память из-
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
145

под объекта не освобождает.
Деструкторы рекомендуется делать виртуальными, для того чтобы при
вызове всегда выполнялся деструктор, соответствующий типу объекта.
Деструкторы обязательно использовать только для динамических
полиморфных объектов, однако можно их применять и для статических
объектов. В объекте можно определить несколько деструкторов
(естественно, в этом случае они должны иметь разные имена).
Вариант кода модуля monsters, содержащий конструкторы и
деструкторы (он используется в следующем разделе), приведен в
пример 7.3.
unit monsters;
interface
uses Graph;
type pmonster = ^monster;
monster = object
constructor init(x_, y_, health_, ammo_ : word);
procedure attack; virtual;
procedure draw; virtual;
procedure erase; virtual;
procedure hit;
procedure move(x_, y_ : word);
destructor done;
private
x, y : word;
health, ammo : word;
color : word;
end;
pdaemon = ^daemon;
daemon = object (monster)
constructor init(x_, y_, health_, ammo_, magic_ : word);
procedure attack; virtual;
procedure draw; virtual;
procedure erase; virtual;
procedure wizardry;
private
magic : word;
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
146

end;
implementation
{ ------------------- реализация методов объекта monster ---------------------- }
constructor monster.init(x_, y_, health_, ammo_ : word);
begin
x := x_; y := y_;
health := health_;
ammo := ammo_;
color := yellow;
end;
procedure monster.attack; { -------------------------------- monster.attack --- }
begin
if ammo = 0 then exit;
dec(ammo); setcolor(color); outtextXY(x + 15, y, 'ба-бах!');
end;
procedure monster.draw; { -------------------------------- monster.draw ----- }
begin
setcolor(color); outtextXY(x, y, '@');
end;
procedure monster.erase; { ---------------------------------- monster.erase --- }
begin
setcolor(black); outtextXY(x, y, '@');
end;
procedure monster.hit; { ---------------------------------- monster.hit ----- }
begin
if health = 0 then exit;
dec(health);
if health = 0 then begin color := red; draw; exit; end;
attack;
end;
procedure monster.move(x_, y_ : word); { --------------------- monster.move --- }
begin
if health = 0 then exit;
erase;
x := x_; y := y_;
draw;
end;
destructor monster.done; { ----------------------------------- monster.done --- }
begin
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
147

end;
{ ----------------------- реализация методов объекта daemon ------------------- }
constructor daemon.init(x_, y_, health_, ammo_, magic_ : word);
begin
inherited init(x_, y_, health_, ammo_);
color := green;
magic := magic_;
end;
procedure daemon.draw; { ----------------------------------- daemon.draw ---- }
begin
setcolor(color); outtextXY(x, y, '%)');
end;
procedure daemon.erase; { ----------------------------------- daemon.erase --- }
begin
setcolor(black); outtextXY(x, y, '%)');
end;
procedure daemon.attack; { ---------------------------------- daemon.attack --- }
begin
if ammo = 0 then exit;
dec(ammo);
if magic > 0 then begin
outtextXY(x + 15, y, 'БУ-БУХ!'); dec(magic); end
else outtextXY(x + 15, y, 'бу-бух!');
end;
procedure daemon.wizardry; { -------------------------------- daemon.wizardry - }
begin
if magic = 0 then exit;
outtextXY(x + 15, y, 'крибле-крабле-бумс!'); dec(magic);
end;
end.
Листинг 7.3. Модуль monsters, использующий конструкторы и
деструкторы
Для использования этого модуля не обязательно иметь в распоряжении
его полный исходный код — достаточно интерфейсного раздела (и,
конечно, файла .tpu ). В программе, использующей этот модуль, можно
описывать производные классы, в которых добавлены новые поля и
методы и переопределены имеющиеся. При этом новые объекты будут
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
148

перемещаться с помощью метода, который был написан до их
появления!
Контейнер для полиморфных объектов
Если объект предназначается для хранения других объектов, он
называется контейнером. Объекты в контейнере могут храниться в виде
массива, списка, стека или другой динамической структуры. Методы
контейнера обычно включают его создание, дополнение, просмотр, а
также поиск и удаление элементов.
В качестве примера контейнера рассмотрим объект list,
предназначенный для работы со связным списком объектов класса
monster и его потомков:
type list = object
constructor init;
procedure add(pm : pmonster);
procedure draw;
destructor done;
private
beg : pnode;
end;
В объекте одно поле beg — указатель на начало списка элементов
типа node:
type pnode = ^node;
node = record
pm : pmonster; { указатель на объект pmonster }
next : pnode; { указатель на следующий элемент списка }
end;
Структура объекта list поясняется на рис. 7.5.
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
149

Рис. 7.5. Список полиморфных объектов
В программе (пример 7.4) создается список из n объектов. Вследствие
расширенной совместимости типов методу add может быль передан
указатель на любой тип, производный от monster. Объекты
выводятся на экран, при этом в одном и том же цикле вызываются
разные виртуальные методы draw в соответствии с фактическим
типом объекта. Затем динамическая память освобождается с помощью
одного вызова деструктора.
program demo_list;
uses graph, crt, monsters;
const n = 30;
type pnode = ^node;
node = record
pm : pmonster;
next : pnode;
end;
list = object
constructor init;
procedure add(pm : pmonster);
procedure draw;
destructor done;
private
beg : pnode;
end;
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
150
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
