Я завершила экзамен 90 баллов на 5. Сертификат не заказала. Сейчас пытаюсь найти как его заказать. у меня указано экзамен пройден баллы оценка видно, а чтоб заказать сертификат нигде не видно. |
Иерархии объектов. Работа с объектами в динамической памяти
Позднее связывание. Виртуальные методы
При раннем связывании программа, готовая для выполнения, представляет собой структуру, логика выполнения которой жестко определена. Если же требуется, чтобы решение о том, какой из одноименных методов разных объектов иерархии использовать, принималось в зависимости от конкретного объекта, для которого выполняется вызов, то ясно, что заранее связывать жестко эти методы с остальной частью кода нельзя. Следовательно, надо каким-то образом дать знать компилятору, что эти методы будут обрабатываться по-другому. Для этого в Паскале существует директива virtual. Она записывается в заголовке метода, например:
procedure attack; virtual;
Слово virtual в переводе с английского значит "фактический". Объявление метода виртуальным означает, что все ссылки на этот метод будут разрешаться по факту его вызова, то есть не на стадии компиляции, а во время выполнения программы. Этот механизм называется поздним связыванием. Для его реализации необходимо, чтобы адреса виртуальных методов хранились там, где ими можно будет в любой момент воспользоваться, поэтому компилятор формирует для этих методов таблицу виртуальных методов (VMT — virtual method table). В первое поле этой таблицы записывается размер объекта, а затем идут адреса виртуальных методов (в том числе и унаследованных) в порядке описания в объекте. Для каждого объектного типа создается одна VMT.
Каждый объект во время выполнения программы должен иметь доступ к VMT. Обеспечение этой связи нельзя поручить компилятору, так как она должна устанавливаться позже — при создании объекта во время выполнения программы. Поэтому связь экземпляра объекта с VMT устанавливается с помощью специального метода, называемого конструктором.
Класс, имеющий хотя бы один виртуальный метод, должен содержать конструктор:
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 в специальное поле объекта (память под это поле выделяется компилятором). Следовательно, прежде чем использовать виртуальные методы, необходимо вызвать конструктор объекта.
Конструктор обычно используется для инициализации объекта. В нем выполняется выделение памяти под динамические переменные или структуры, если они есть в объекте, и присваиваются начальные значения. Если в объекте есть поля, которые также являются объектами, в конструкторе вызываются конструкторы этих объектов.
Объект может содержать несколько конструкторов. Повторный вызов конструктора вреда программе не наносит, а вот если конструктор вообще не вызвать и попытаться использовать виртуальный метод, поведение программы не определено. Конструктор должен быть вызван для каждого создаваемого объекта. Присваивание одного объекта другому возможно только после конструирования обоих.
Вызов виртуального метода выполняется так: из объекта берется адрес его VMT, из VMT выбирается адрес метода, а затем управление передается этому методу ( рис. 7.2). Таким образом, при использовании виртуальных методов из всех одноименных методов иерархии всегда выбирается тот, который соответствует фактическому типу вызвавшего его объекта.
Поскольку связь с VMT устанавливается в самом начале конструктора, в его теле также можно пользоваться виртуальными методами.
Правила описания виртуальных методов:
- Если в объекте метод определен как виртуальный, во всех потомках он также должен быть виртуальным. Отсутствие ключевого слова virtual в заголовке метода потомка приведет к ошибке.
- Заголовки всех одноименных виртуальных методов должны совпадать (параметры должны иметь одинаковый тип, количество и порядок следования, функции должны возвращать значение одного и того же типа).
- Переопределять виртуальный метод в каждом из потомков не обязательно: если он выполняет устраивающие потомка действия, он будет унаследован.
- Объект, имеющий хотя бы один виртуальный метод, должен содержать конструктор.
Для иллюстрации работы виртуальных методов используем программу из предыдущей лекции, сменив в ней тип элементов массива с 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 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, а не непосредственно.