Вопросы:
Что, если в производных классах Child и GrandChild опустить ключевое слово virtual перед функцией-членом answerName?
Программа будет работать отлично. Ключевое слово virtual необходимо в полиморфическом кластере только перед виртуальной функцией самого верхнего уровня. Для всех остальных переопределений этой функции ключевое слово virtual необязательно.
Что, если убрать ключевое слово virtual перед всеми функциями-членами answerName?
На выходе получим:
My last name is Иванов
My last name is Иванов
My last name is Иванов
Для реализации полиморфизма можно использовать ссылки на объекты вместо указателей.
void main(void) {
Parent p(“Иванов”);
Child c(“Иванов”,”Сергей”);
CrandChild g(“Иванов”,”Андрей”,”Владимир”);
Parent &f0=p, &f1=c; &f2=g;
f0.answerName();f1.answerName();f2.answerName();
}
Рассмотрим тонкости виртуальной функции
class P {
private:
virtual void method1(void) {}
void method2(void){}
void method3(void){method1(); method2();} // this->method1, this->method2
void method5(void){}
public:
void method6(void){method3();}
void method4(void){}
void method7(void){method5(); }
};
class C:public P {
private:
void method1(void) {}
void method5(void) {}
public:
C(){}
void method4(void) {}
void method7(void) { method5(); }
};
void main() {
P pop;
C me;
pop.method6(); // P::method1, P::method2
me.method6(); // C::method1, P::method2
pop.method4(); // P::method4
me.method4(); // C::method4
pop.method7(); // P::method5
me.method7(); // C::method5
}
Сообщение method6() посылается объекту me. Это сообщение унаследовано из класса P. Вызывается защищенный метод method3() и, затем, защищенные методы method1() и method2(). Так как method1() объявлен в базовом классе Р виртуальным, система на этапе выполнения программы связывает сообщение this->method1() с method1() класса C.
При перегрузке функции-члена виртуальный функции способны вызвать смешивание и беспорядок.
class B {
public:
virtual foo(int);
virtual foo(double);
};
class D: public B {
public:
foo(int);
};
void main() {
D d; B b, *pb=&d;
b.foo(9); // B::foo(int);
b.foo(9.5); // B::foo(double)
pb->foo(9.5); // B::foo(double);
pb->foo(9); // D::foo(int)
}
Функция-член базового класса B::foo(int) в порожденном переопределена. Функция-член базового класса B::foo(double) в порожденном классе скрыта.
Virtual могут быть только нестатические функции-члены. Функция порожденного класса автоматически становится виртуальной. Специальным ограничением является: деструкторы могут быть виртуальными, а конструкторы - нет.
Рассмотрим пример преимущества использования виртуальных функций для упрощения кода:
class shape {
protected:
double x,y;
public:
virtual double area() { return 0.; }
};
class rectangle: public shape {
double height,width;
public:
double area() { return (height*width); }
};
class circle: public shape {
double radius;
public:
double area() { return (PI*radius*radius); }
};
Вычисление площади является локальной ответственностью порожденного класса. Код пользователя, который использует полиморфное вычисление площади, может выглядеть так:
shape *p[N];
..........
for(i=0; i<N; i++) tot_area+=p[i]->area();
Здесь главное преимущество состоит в том, что код пользователя не нуждается в изменении, даже если к системе добавляются новые формы. Изменения управляются локально и распространяются автоматически, благодаря полиморфному характеру кода пользователя.