- •Полиморфизм
- •Способность объектно-ориентированных языков автоматически определять тип объекта
- •Виртуальные функции имеет смысл использовать только для классов, являющихся базовыми.
- •Правило: указатель на базовый класс может ссылаться на объект этого класса или любого
- •Пример. Использование указателей на классы.
- •int main(){
- •Пример. Использование виртуальных функций.
- •void sound(Animal& i)
- •Переопределим класс Animal:
- •Пример. Использование указателей на объекты. Приведение типов.
- •class Dog: public Animal
- •int main()
- •Механизм работы позднего связывания
- •В каждом классе также помещается указатель VFPTR на таблицу виртуальный функций.
- •При явном связывании мы получаем
- •Для указателей, имеющих тип базового класса, компилятор запрещает вызовы тех виртуальных функций, которые
- •Абстрактные базовые классы
- •Пример. Работа с абстрактным классом
- •Чистые виртуальные функции могут быть определены, но только вне объявления класса.
- •class Dog: public Animal
- •char *eat() {
- •Виртуальные деструкторы class Base1
- •class Base2
- •int main()
- •Чистые виртуальные деструкторы
- •Виртуальные функции в деструкторах
- •int main {
- •Пример. Перегруженные переопределённые функции
- •class Derived2: public Base { public:
- •int main() { char *s;
- •Множественное наследование
- •Вызов конструкторов при создании объекта класса D происходит в том порядке, в котором
- •class A { public:
- •class C: public A, public B { public:
- •Виртуальный базовый класс
- •class A:public virtual W { public:
- •int main() {
- •Виртуальные базовые классы инициализируются перед любыми невиртуальными базовыми классами в том порядке, в
- •Пример. Использование одного класса в качестве виртуального и невиртуального базового класса
Пример. Использование указателей на объекты. Приведение типов.
class Animal { protected:
char *pname; public:
Animal(char *AnName) {
pname=new char[strlen(AnName)+1]; strcpy(pname, AnName);
}
virtual char* speak() {return "Не известно";} virtual char *name() {return pname;}
};
class Dog: public Animal
{
public:
Dog(char *name):Animal(name){} char * speak() {
char *phrase; phrase=strdup(pname);
return strcat(phrase," говорит Гав!");
} |
|
virtual char * sit() { |
|
char *phrase; |
|
phrase=strdup(pname); |
|
return strcat(phrase," сидит"); |
} |
};
int main()
{
Animal* p[2]={new Animal("a"), new Dog("Шарик")};
printf("%s\n",p[0]->speak()); printf("%s\n",p[1]->speak()); //printf("%s\n",p[1]->sit()); //ошибка
компиляции: 'sit' : is not a member of 'Animal' printf("%s\n",((Dog*)p[1])->sit());
return 0;
}
Механизм работы позднего связывания
Компилятор для каждого класса (не объекта),
содержащего хотя бы один виртуальный метод, создает таблицу виртуальных функций VFTABLE.
В эту таблицу помещаются адреса виртуальных функций класса в порядке их описания.
Адрес любого виртуального метода имеет в VFTABLE одно и то же смещение для каждого класса в пределах иерархии.
В каждом классе также помещается указатель VFPTR на таблицу виртуальный функций.
Схема работы с VFTABLE для объектов из предыдущего примера:
|
Объект класса Animal |
[0] |
&Animal::speak() |
|
P |
vfptr |
[1] |
&Animal::name() |
|
P0 |
|
[0] |
&Dog::speak() |
|
|
|
|
||
P1 |
Объект класса Dog |
[1] |
&Animal::name() |
|
vfptr |
[2] |
&Dog::sit() |
||
|
||||
|
|
|
При явном связывании мы получаем
абсолютные адреса функций.
При неявном связывании, вызывая функцию
name(), говорят, что надо вызвать функцию по адресу VFPTR+1.
Наследование и таблица виртуальных функций
&Animal::speak () &Dog::speak() &Animal::name () &Animal::name()
&Dog::sit()
Для указателей, имеющих тип базового класса, компилятор запрещает вызовы тех виртуальных функций, которые присутствуют только в производном классе.
Так как у базового класса Animal нет функции
sit(), то компилятор выдает ошибку при попытке ее использования (p[1]->sit()).
Чтобы для p[1] вызвать функцию sit(), необходимо использовать явное приведение типов:
((Dog*) p[1])->sit();
Абстрактные базовые классы
Класс становится абстрактным, когда в него добавляется чистая виртуальная функция:
virtual void f() = 0;
Нельзя создавать объекты абстрактного базового класса.
Когда абстрактный класс наследуется, все чистые виртуальные функции должны быть переопределены, иначе производный класс становится абстрактным.
Пример. Работа с абстрактным классом
class Animal { protected:
char *pname; public:
Animal(char *AnName)
{
pname=new char[strlen(AnName)+1]; strcpy(pname, AnName);
}
virtual char* speak()=0; virtual char *eat()=0;
virtual char *name() {return pname;}
};
Чистые виртуальные функции могут быть определены, но только вне объявления класса.
Определение чистых виртуальных функций в базовом классе не дает права создавать объект абстрактного базового класса.
Используются, когда существует общий фрагмент кода, который нет необходимости дублировать в каждом переопределённом методе классов-потомков.
char* Animal::speak() {return "Animal::speak()";}
char* Animal::eat() {return "Animal::eat()";}