Вызов элемент-функций производного класса через указатели базового класса



Пример 3

Через указатель базового класса компилятор позволяет нам вызывать только элемент-функции базового класса. Таким образом, если указатель базового класса ссылается на объект производного класса, попытка обращения через него к функции, имеющейся только в производном классе вызовет ошибку компиляции.

Пример3 показывает последствия попытки вызова функции производного класса через указатель базового класса. [Замечание. Мы снова используем классы CommissionEmployee и BasePlusCommissionEmployee из Примера 1.] Строка 9 создает commissionEmployeePtr — указатель на объект CommissionEmployee, — а строки 10-11 создают объект класса BasePlusCommissionEmployee. Строка 14 устанавливает commissionEmployeePtr на объект производного класса basePlusCommissionEmployee. Как вы помните из Примера 1, компилятор позволяет это сделать, так как   BasePlusCommissionEmployee является CommissionEmployee (в том смысле, что объект BasePlusCommissionEmployee обладает всеми функциональными свойствами объекта CommissionEmployee). Строки 18-22 вызывают через указатель базового класса элемент-функции базового класса getFirstName, getLastName, getSocialSecurityNumber, getGrossSales и getCommissionRate. Все эти вызовы законны, так как класс BasePlusCommissionEmployee наследует эти функции от CommissionEmployee. Мы знаем, что commissionEmployeePtr ссылается на объект BasePlusCommissionEmployee, поэтому в строках 26-27 мы пытаемся вызвать элемент-функции getBaseSalary и setBaseSalary. Компилятор генерирует ошибку для обеих этих строк, так как вызываемые функции не являются элементами базового класса CommissionEmployee. Дескриптор может активировать только те функции, что являются элементами класса, ассоциированного с данным дескриптором. (В данном случае через CommissionEmployee * мы можем вызывать только элемент-функции CommissionEmployee — setFirstName, getFirstName, setLastName, getLastName, setSocialSecurityNumber, getSocialSecurityNumber, setGrossSales, getGrossSales, setCommissionRate, getCommissionRate и print.)

Оказывается, что компилятор C++ позволяет обращаться к элементам, входящим только в производный класс, из указателя базового класса, ссылающегося на производный класс, лишь при условии явного приведения указателя базового класса к типу производного класса; это называют нисходящим приведением. Как вы узнали в Примере 1, можно установить указатель базового класса на объект производного класса. Однако как показывает Пример 3, указатель базового класса может использоваться лишь для вызова функций, объявленных в базовом классе. Нисходящее приведение позволяет программе производить специфические для производного класса операции над объектом производного класса, на который ссылается указатель базового класса. После нисходящего приведения программа может вызывать функции производного класса, которые не входят в базовый класс. В следующем мы покажем конкретный пример нисходящего приведения.

Если адрес объекта производного класса был присвоен указателю одного из его непосредственных или косвенных базовых классов, возможно обратное приведение того указателя к типу указателя производного класса. На самом деле это необходимо сделать, чтобы можно было посылать объекту производного класса сообщения, не определенные в базовом классе.

 

 

Виртуальные функции

В Примере 1 мы устанавливали указатель базового класса CommissionEmployee на объект производного класса BasePlusCommissionEmployec, а затем вызывали через этот указатель элемент-функцию print. Вспомните, что тип дескриптора определяет, функция какого класса будет вызвана. В данном случае указатель CommissionEmployee вызывал функцию print класса CommissionEmployee на объекте производного класса BasePlusCommissionEmployee, хотя указатель ссылался на объект BasePlusCommissionEm

ployee, имеющий свою собственную версию print. В случае виртуальных функций не тип дескриптора, а тип указываемого объекта определяет, функция какого класса будет вызвана.

Рассмотрим сначала, почему виртуальные функции полезны. Предположим, что от одного класса Shape производится ряд классов геометрических фигур, например, Circle, Triangle, Rectangle и Square. В объектно-ориентированном программировании каждый из этих классов может быть наделен способностью нарисовать самого себя. Хотя каждый класс имеет собственную функцию draw, эти функции существенно различаются. При рисовании фигуры, чем бы она ни являлась, было бы удобно рассматривать ее обобщенно, как объект базового класса Shape. Тогда, чтобы нарисовать любую фигуру, мы могли бы просто вызвать функцию draw класса Shape, предоставив программе динамически (т.е. во время выполнения) определить, которую из функций draw производных классов использовать, в зависимости от типа объекта, на который в тот или иной момент ссылается указатель базового класса Shape. Чтобы реализовать такого рода поведение, мы объявляем функцию draw в базовом классе как виртуальную функцию и затем заменяем ее в каждом из производных классов функцией, которая может нарисовать соответствующую фигуру. Функция объявляется виртуальной указанием ключевого слова virtual перед прототипом функции в базовом классе. Например, класс Shape может содержать такое объявление:

 

virtual void draw() const;

 

Этот прототип объявляет draw виртуальной константной функцией, не принимающей аргументов и ничего не возвращающей. Функция объявляется как const, так как типичная функция draw не изменяла бы объект Shape, для которого вызывается. Виртуальные функции не обязательно должны быть константными.

 

Если функция объявляется, как virtual, она остается виртуальной во всей иерархии наследования ниже данной точки, даже если она не объявляется явно виртуальной в классах, которые ее переопределяют.

Хотя некоторые функции являются неявно виртуальными благодаря объявлению в некотором базовом классе, ради ясности программы явно объявляйте эти функции как virtual на всех уровнях иерархии.

Если в производном классе виртуальная функция не заменяется, то он просто наследует реализацию виртуальной функции своего базового класса.

Если функция draw объявлена в базовом классе как виртуальная, и мы вызываем ее через указатель или ссылку базового класса на объект производного класса (например, shapePtr—>draw()),To программа динамически (т.е. во время исполнения) выберет функцию draw нужного производного класса — в зависимости от типа объекта, а не типа указателя или ссылки. Выбор нужной функции во время исполнения (а не во время компиляции) называется динамическим или поздним связыванием.

Если виртуальная функция вызывается через имя объекта с операцией-точкой выбора элемента (например, squareObject.draw()), то ссылка разрешается во время компиляции (это называется статическим связыванием) и вызывается виртуальная функция, определенная (или унаследованная) классом, которому принадлежит данный объект; это не является полиморфным поведением. Таким образом, динамическое связывание виртуальных функций имеет место только в случае использования дескрипторов-указателей (или ссылок, как мы скоро увидим).

 


Дата добавления: 2018-08-06; просмотров: 290; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!