Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на языке высокого уровня Паскаль. Учебное пособие для СПО

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
объекта, переопределим только те, которые реализуются по-другому (пример 7.1).
type daemon = object (monster) procedure init(x_, y_, health_, ammo_, magic_ : word); procedure attack; procedure draw; procedure erase; procedure wizardry; private magic : word; end; { ------------------------- реализация методов объекта daemon ----------------- } procedure daemon.init(x_, y_, health_, ammo_, magic_ : word); begin inherited init(x_, y_, health_, ammo_); color := green; magic := magic_; 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.draw; { ----------------------------------- daemon.draw ---- } begin setcolor(color); outtextXY(x, y, '%)'); end; procedure daemon.erase; { ----------------------------------- daemon.erase ---- } begin setcolor(black); outtextXY(x, y, '%)'); end; procedure daemon.wizardry; { -------------------------------- daemon.wizardry - } begin if magic = 0 then exit;
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
131
outtextXY(x + 15, y, 'крибле-крабле-бумс!'); dec(magic); end;
Листинг 7.1. Переопределение методов после добавления нового типа персонажей
Наследование полей.Унаследованные поля доступны в объекте точно так же, как и его собственные. Изменить или удалить поле при наследовании нельзя.Объект daemon содержит все поля своего предка и одно собственное поле magic, в котором хранится "магическая сила" объекта.
Наследование методов.В потомке объекта можно не только описывать новые методы, но и переопределять существующие. Метод можно переопределить либо полностью, либо дополнив метод предка.
В объекте daemon описан новый метод wizardry, с помощью которого объект применяет свою магическую силу, а метод инициализации init переопределен, потому что количество полей объекта изменилось. Однако необходимость задавать значения унаследованным полям осталась, и соответствующий метод есть в объекте monster, поэтому из нового метода инициализации сначала вызывается старый, а затем выполняются дополнительные действия (присваивание значения полю ammo ).
Вызов метода предка из метода потомка выполняется с помощью ключевого слова inherited (унаследованный). Можно вызвать метод предка и явным образом с помощью конструкции
monster.init.
Методы отрисовки draw и erase также переопределены, потому что изображение демона отличается от изображения монстра и, следовательно, формируется другой последовательностью подпрограмм (для простоты представим демона в виде "смайлика").
Переопределен и метод attack: теперь атака выполняется по-разному в зависимости от наличия магической силы.
Чтобы перемещать демона, требуется выполнить те же действия, что записаны в методе move для перемещения монстра: необходимо
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
132
стереть его изображение на старом месте, обновить координаты и нарисовать на новом месте. На первый взгляд, можно без проблем унаследовать этот метод, а также метод hit. Так мы и поступим.
Добавим описание объекта daemon в интерфейсную часть модуля
monsters, а тексты его методов — в раздел реализации. Проверим
работу новых методов с помощью программы:
program test_inheritance; uses graph, crt, monsters; var Vasia : daemon; gd, gm : integer; begin gd := detect; initgraph(gd, gm, '...'); if graphresult <> grOk then begin writeln('ошибка инициализации графики'); exit end; Vasia.init(100, 100, 20, 10, 6); Vasia.draw; Vasia.attack; readln; Vasia.erase; readln; end.
И в предке, и в потомке есть одноименные методы. Вызывается всегда тот метод, который соответствует типу объекта, потому что при вызове указывается имя экземпляра заданного типа (рис. 7.1). Это можно рассматривать как простейший вид полиморфизма.
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
133
Рис. 7.1. Раннее связывание
Раннее связывание
Продолжим тестирование объекта daemon, вставив в приведенную выше программу перед первой из процедур readln вызовы методов, унаследованных из объекта monster.
Vasia.move(200, 100); Vasia.move(200, 200); Vasia.hit;
Результаты запуска программы разочаровывают: на экране появляется изображение не демона, а монстра — символ @! Значит, из метода
move вызываются методы рисования и стирания объекта-предка. Да и
метод атаки, вызываемый из hit, судя по диагностическому сообщению, также принадлежит объекту monster. Чтобы разобраться, отчего это происходит, рассмотрим механизм работы компилятора.
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
134
Исполняемые операторы программы в виде инструкций процессору находятся в сегменте кода. Каждая подпрограмма имеет точку входа. Вызов подпрограммы при компиляции заменяется на последовательность команд, которая передает управление в эту точку, а также выполняет передачу параметров и сохранение регистров процессора. Этот процесс называется разрешением ссылок и в других языках чаще всего выполняется не компилятором, а специальной программой — редактором связей, или компоновщиком.
Таким образом, при компиляции метода move объекта monster на место вызова методов erase и draw вставляются переходы на первые исполняемые операторы этих методов из объекта monster. Вызвав метод move из любого потомка monster, мы в любом случае попадем в методы erase и draw объекта monster, потому что они жестко связаны друг с другом еще до выполнения программы (рис. 7.1).
Аналогичная ситуация и с методом attack. Если он вызывается непосредственно для экземпляра объекта daemon, то все в порядке, но вызвать его из метода hit, описанного в объекте-предке, невозможно, потому что при компиляции метода hit в него была вставлена передача управления на метод attack объекта monster (рис. 7.1).
Этот механизм называется ранним связыванием, так как все ссылки на подпрограммы компилятор разрешает до выполнения программы. Ясно, что с помощью раннего связывания не удастся обеспечить возможность вызова из одной и той же подпрограммы метода то одного объекта, то другого. Это можно сделать только в случае если ссылки будут разрешаться на этапе выполнения программы в момент вызова метода. Такой механизм в Паскале есть: он называется поздним связыванием и реализуется с помощью так называемых виртуальных методов. Но перед тем как заняться их изучением, надо рассмотреть вопрос о совместимости типов объектов.
Совместимость типов объектов
Паскаль — язык со строгой типизацией. Операнды, участвующие в выражениях, параметры подпрограмм и их аргументы, левая и правая части оператора присваивания должны подчиняться правилам
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
135
соответствия типов. Для объектов понятие совместимости расширено: производный тип совместим со своим родительским типом. Эта расширенная совместимость типов имеет три формы:
между экземплярами объектов; между указателями на экземпляры объектов; между параметрами и аргументами подпрограмм.
Во всех трех случаях совместимость односторонняя: родительскому объекту может быть присвоен экземпляр любого из его потомков, но не наоборот. Это связано с тем, что при присваивании должны быть заполнены все поля, а потомок имеет либо такой же размер, как предок, либо больший.
Например, если определены переменные:
type pmonster = ^monster; pdaemon = ^daemon; var m : monster; d : daemon; pm : pmonster; pd : pdaemon;
то приведенные ниже операторы присваивания допустимы:
m := d; pm := pd;
Поля и методы, введенные в потомке, после таких присваиваний недоступны, потому что объекты базового класса не имеют информации о существовании элементов, определенных в производном. Например, обращение pm^.wizardry ошибочно несмотря на то, что на самом деле указатель pm ссылается на объект типа daemon.
Даже если метод переопределен в потомке, через указатель на предка вызывается метод, описанный в предке. Так, в результате выполнения оператора pm^.draw на экране появится изображение объекта-предка — символ @, потому что тип вызываемого метода соответствует типу указателя, а не типу того объекта, на который он ссылается.
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
136
Если известно, что указатель на предка на самом деле хранит ссылку на потомка, можно обратиться к элементам, определенным в потомке, с помощью явного преобразования типа, например
pdaemon(pm)^.wizardry.
Если объект является параметром подпрограммы, ему может соответствовать аргумент того же типа или типа любого из его потомков, но есть разница между передачей объектов по значению и по адресу.
Параметр, передаваемый по значению, представляет собой копию объекта-аргумента, содержащую только те поля данных и методы, которые имеются в объекте-параметре. Это значит, что при передаче по значению тип аргумента приводится к типу параметра.
При передаче объекта по адресу подпрограмме передается указатель на фактический объект, то есть приведение типов не выполняется. В подпрограмме тип объекта, передаваемого по адресу, может изменяться в зависимости от аргумента.
Если параметр подпрограммы является указателем на объект, передаваемым по значению, то аргумент может быть указателем как на этот же объект, так и на любого из его потомков. Например, если заголовок процедуры имеет вид
procedure checkp(p1 : pmonster; var p2 : pmonster);
первым параметром в нее можно передавать указатели как на объекты типа monster, так и на любые производные объекты. На месте второго параметра может быть только указатель типа pmonster.
Объекты, фактический тип которых может изменяться во время выполнения программы, называются полиморфными. Полиморфным может быть объект, определенный через указатель или переданный в подпрограмму по адресу.
Полиморфные объекты широко применяются в программах, потому что они обеспечивают гибкость: например, список, содержащий указатели на объект базового класса, может на самом деле хранить ссылки на
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
137
любые объекты иерархии. Подпрограммы, параметрами которых являются полиморфные объекты, могут без изменений и даже без перекомпиляции использоваться для объектов, о существовании которых при написании подпрограммы еще ничего не было известно.
Полиморфные объекты обычно применяются вместе с виртуальными методами, которые мы рассмотрим в следующем разделе.
Позднее связывание. Виртуальные методы
При раннем связывании программа, готовая для выполнения, представляет собой структуру, логика выполнения которой жестко определена. Если же требуется, чтобы решение о том, какой из одноименных методов разных объектов иерархии использовать, принималось в зависимости от конкретного объекта, для которого выполняется вызов, то ясно, что заранее связывать жестко эти методы с остальной частью кода нельзя. Следовательно, надо каким-то образом дать знать компилятору, что эти методы будут обрабатываться по­другому. Для этого в Паскале существует директива virtual. Она записывается в заголовке метода, например:
procedure attack; virtual;
Слово virtual в переводе с английского значит "фактический". Объявление метода виртуальным означает, что все ссылки на этот метод будут разрешаться по факту его вызова, то есть не на стадии компиляции, а во время выполнения программы. Этот механизм называется поздним связыванием. Для его реализации необходимо, чтобы адреса виртуальных методов хранились там, где ими можно будет в любой момент воспользоваться, поэтому компилятор формирует для этих методов таблицу виртуальных методов (VMT — virtual method table). В первое поле этой таблицы записывается размер объекта, а затем идут адреса виртуальных методов (в том числе и унаследованных) в порядке описания в объекте. Для каждого объектного типа создается одна VMT.
Каждый объект во время выполнения программы должен иметь доступ к VMT. Обеспечение этой связи нельзя поручить компилятору, так как она должна устанавливаться позже — при создании объекта во время выполнения программы. Поэтому связь экземпляра объекта с VMT
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
138
устанавливается с помощью специального метода, называемого конструктором.
Класс, имеющий хотя бы один виртуальный метод, должен содержать конструктор:
type 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); private x, y : word; health, ammo : word; color : word; end; 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; end;
По ключевому слову constructor компилятор вставляет в начало метода фрагмент, который записывает ссылку на VMT в специальное поле объекта (память под это поле выделяется компилятором). Следовательно, прежде чем использовать виртуальные методы, необходимо вызвать конструктор объекта.
Конструктор обычно используется для инициализации объекта. В нем выполняется выделение памяти под динамические переменные или структуры, если они есть в объекте, и присваиваются начальные значения. Если в объекте есть поля, которые также являются объектами, в конструкторе вызываются конструкторы этих объектов.
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
139
Объект может содержать несколько конструкторов. Повторный вызов конструктора вреда программе не наносит, а вот если конструктор вообще не вызвать и попытаться использовать виртуальный метод, поведение программы не определено. Конструктор должен быть вызван для каждого создаваемого объекта. Присваивание одного объекта другому возможно только после конструирования обоих.
Вызов виртуального метода выполняется так: из объекта берется адрес его VMT, из VMT выбирается адрес метода, а затем управление передается этому методу (рис. 7.2). Таким образом, при использовании виртуальных методов из всех одноименных методов иерархии всегда выбирается тот, который соответствует фактическому типу вызвавшего его объекта.
Рис. 7.2. Позднее связывание
Поскольку связь с VMT устанавливается в самом начале конструктора, в его теле также можно пользоваться виртуальными методами.
Правила описания виртуальных методов:
Если в объекте метод определен как виртуальный, во всех потомках он также должен быть виртуальным. Отсутствие ключевого слова virtual в заголовке метода потомка приведет к ошибке.
Программирование на я зыке высокого уровня ПаскальТ.А. Павловская
140
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]