Я завершила экзамен 90 баллов на 5. Сертификат не заказала. Сейчас пытаюсь найти как его заказать. у меня указано экзамен пройден баллы оценка видно, а чтоб заказать сертификат нигде не видно. |
Иерархии объектов. Работа с объектами в динамической памяти
Раннее связывание
Продолжим тестирование объекта daemon, вставив в приведенную выше программу перед первой из процедур readln вызовы методов, унаследованных из объекта monster.
Vasia.move(200, 100); Vasia.move(200, 200); Vasia.hit;
Результаты запуска программы разочаровывают: на экране появляется изображение не демона, а монстра — символ @! Значит, из метода move вызываются методы рисования и стирания объекта-предка. Да и метод атаки, вызываемый из hit, судя по диагностическому сообщению, также принадлежит объекту monster. Чтобы разобраться, отчего это происходит, рассмотрим механизм работы компилятора.
Исполняемые операторы программы в виде инструкций процессору находятся в сегменте кода. Каждая подпрограмма имеет точку входа. Вызов подпрограммы при компиляции заменяется на последовательность команд, которая передает управление в эту точку, а также выполняет передачу параметров и сохранение регистров процессора. Этот процесс называется разрешением ссылок и в других языках чаще всего выполняется не компилятором, а специальной программой — редактором связей, или компоновщиком.
Таким образом, при компиляции метода move объекта monster на место вызова методов erase и draw вставляются переходы на первые исполняемые операторы этих методов из объекта monster. Вызвав метод move из любого потомка monster, мы в любом случае попадем в методы erase и draw объекта monster, потому что они жестко связаны друг с другом еще до выполнения программы ( рис. 7.1).
Аналогичная ситуация и с методом attack. Если он вызывается непосредственно для экземпляра объекта daemon, то все в порядке, но вызвать его из метода hit, описанного в объекте-предке, невозможно, потому что при компиляции метода hit в него была вставлена передача управления на метод attack объекта monster ( рис. 7.1).
Этот механизм называется ранним связыванием, так как все ссылки на подпрограммы компилятор разрешает до выполнения программы. Ясно, что с помощью раннего связывания не удастся обеспечить возможность вызова из одной и той же подпрограммы метода то одного объекта, то другого. Это можно сделать только в случае если ссылки будут разрешаться на этапе выполнения программы в момент вызова метода. Такой механизм в Паскале есть: он называется поздним связыванием и реализуется с помощью так называемых виртуальных методов. Но перед тем как заняться их изучением, надо рассмотреть вопрос о совместимости типов объектов.
Совместимость типов объектов
Паскаль — язык со строгой типизацией. Операнды, участвующие в выражениях, параметры подпрограмм и их аргументы, левая и правая части оператора присваивания должны подчиняться правилам соответствия типов. Для объектов понятие совместимости расширено: производный тип совместим со своим родительским типом. Эта расширенная совместимость типов имеет три формы:
- между экземплярами объектов;
- между указателями на экземпляры объектов;
- между параметрами и аргументами подпрограмм.
Во всех трех случаях совместимость односторонняя: родительскому объекту может быть присвоен экземпляр любого из его потомков, но не наоборот. Это связано с тем, что при присваивании должны быть заполнены все поля, а потомок имеет либо такой же размер, как предок, либо больший.
Например, если определены переменные:
type pmonster = ^monster; pdaemon = ^daemon; var m : monster; d : daemon; pm : pmonster; pd : pdaemon;
то приведенные ниже операторы присваивания допустимы:
m := d; pm := pd;
Поля и методы, введенные в потомке, после таких присваиваний недоступны, потому что объекты базового класса не имеют информации о существовании элементов, определенных в производном. Например, обращение pm^.wizardry ошибочно несмотря на то, что на самом деле указатель pm ссылается на объект типа daemon.
Даже если метод переопределен в потомке, через указатель на предка вызывается метод, описанный в предке. Так, в результате выполнения оператора pm^.draw на экране появится изображение объекта-предка — символ @, потому что тип вызываемого метода соответствует типу указателя, а не типу того объекта, на который он ссылается.
Если известно, что указатель на предка на самом деле хранит ссылку на потомка, можно обратиться к элементам, определенным в потомке, с помощью явного преобразования типа, например pdaemon(pm)^.wizardry.
Если объект является параметром подпрограммы, ему может соответствовать аргумент того же типа или типа любого из его потомков, но есть разница между передачей объектов по значению и по адресу.
Параметр, передаваемый по значению, представляет собой копию объекта-аргумента, содержащую только те поля данных и методы, которые имеются в объекте-параметре. Это значит, что при передаче по значению тип аргумента приводится к типу параметра.
При передаче объекта по адресу подпрограмме передается указатель на фактический объект, то есть приведение типов не выполняется. В подпрограмме тип объекта, передаваемого по адресу, может изменяться в зависимости от аргумента.
Если параметр подпрограммы является указателем на объект, передаваемым по значению, то аргумент может быть указателем как на этот же объект, так и на любого из его потомков. Например, если заголовок процедуры имеет вид
procedure checkp(p1 : pmonster; var p2 : pmonster);
первым параметром в нее можно передавать указатели как на объекты типа monster, так и на любые производные объекты. На месте второго параметра может быть только указатель типа pmonster.
Объекты, фактический тип которых может изменяться во время выполнения программы, называются полиморфными. Полиморфным может быть объект, определенный через указатель или переданный в подпрограмму по адресу.
Полиморфные объекты широко применяются в программах, потому что они обеспечивают гибкость: например, список, содержащий указатели на объект базового класса, может на самом деле хранить ссылки на любые объекты иерархии. Подпрограммы, параметрами которых являются полиморфные объекты, могут без изменений и даже без перекомпиляции использоваться для объектов, о существовании которых при написании подпрограммы еще ничего не было известно.
Полиморфные объекты обычно применяются вместе с виртуальными методами, которые мы рассмотрим в следующем разделе.