简要回顾一下:
1. 多态发生的条件:
(1)继承;(2)派生类重写基类虚函数;(3)基类指针指向派生类对象。
2. C++多态原理
(1)类中有虚函数,编译阶段会创建虚函数表,该表存放RTTI(运行时类型信息)和所有虚函数的入口地址;编译器在该类的对象中添加虚函数表指针指向虚函数表,虚函数表会加载到.rodata区;
补充:
①该类的多个对象中的虚函数指针指向同一个虚函数表;
②虚函数的个数不影响对象内存大小,但影响虚函数表大小;
(2)派生类继承时,会创建自己的虚函数表指针和虚函数表;若不重写虚函数,则虚函数表中的虚函数入口地址仍是基类中虚函数的入口地址;
补充:
①若派生类中某个函数,与基类继承来的某个虚函数的返回值类型、函数名、参数列表都相同,则派生类的该方法被自动处理为虚函数。
(3)若重写基类的虚函数,则被重写的函数会覆盖掉继承过来的虚函数的入口地址;
3. 动态联编
运行阶段才确定调用哪个函数。普通成员函数加virtual成为虚函数,则编译器运行时才确定调用哪个函数(反汇编时,看到call寄存器,则只有运行时才知道调用的具体函数;若为静态联编,则call一个具体的函数)。
补充:
①指针或引用调用虚函数,才会发生动态绑定。
②只要是指针或引用调用虚函数,不仅是基类指针指向派生类对象,基类指针指向基类对象同样会发生动态绑定。
③对象调用虚函数,为静态绑定。
4. 多态中类型转换问题
(1)派生类转基类:向上转换,编译器会缩小寻址范围,所以是安全的;
(2)反之基类转派生类,指针寻址范围扩大,不安全。
代码示例
class Animal {
public:
virtual void speak() { // 此speak()函数写成虚函数,31、32行才会发生多态
cout << "Animal Speak()." << endl;
}
void speak(int a) { // 仅将该重载的speak()函数写为虚函数,则不会发生多态
cout << "Animal Speak(int a)." << endl;
}
int mA;
};
class Cat :public Animal {
public:
void speak() { // 重写基类虚函数
cout << "Cat speak()" << endl;
}
int mC;
};
void doFunc(Animal* animal) { // 形参中,类型向上转换,寻址范围缩写
animal->speak();
}
void test() {
Cat *c = new Cat;
Animal* animal = new Cat;
// 基类的指针,指向派生类对象空间,子类对象的虚函数表中的speak()函数地址基父类不同,因为被重写了
animal->speak();
doFunc(c);
}