一、多态条件
1.父类的指针或引用
问题1.为什么不能是子类的指针或引用?
因为子类指针只能指向子类对象,而父类指针可以指向父类对象,同时可以通过切片指向子类中继承的父类。
问题2.为什么不能是父类对象?
如上代码,子类赋值给父类对象切片,是不会拷贝子类虚表到父类虚表的,所以就不能通过父类对象访问子类虚函数,从而不能实现多态。如果拷贝虚表,那么父类对象虚表中是父类虚函数还是子类就不确定了,同样不行。
2.虚函数重写
重写的是函数的实现。对象中存的不是虚表,而是虚表指针。虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样,都是存在代码段(常量区),只是他的指针又存到了虚表中。虚表存在常量区(同类型的对象共用虚表)。
总结一下派生类的虚表生成: a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
二、多继承虚表
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() {cout << "Derive::func1" << endl;}
virtual void func3() {cout << "Derive::func3" << endl;}
private:
int d1;
};
//int main()
//{
// Derive d;
// cout << sizeof(d) << endl;
//
// int vft1 = *((int*)&d);
// //int vft2 = *((int*)((char*)&d+sizeof(Base1)));
// Base2* ptr = &d;
// int vft2 = *((int*)ptr);
//
// PrintVFT((FUNC_PTR*)vft1);
// PrintVFT((FUNC_PTR*)vft2);
//
// return 0;
//}
//
int main()
{
Derive d;
Base1* ptr1 = &d;
ptr1->func1();
Base2* ptr2 = &d;
ptr2->func1();
Derive* ptr3 = &d;
ptr3->func1();
return 0;
}
上述代码中,Derive的虚表存储方式如下图所示:
可以看到base1和base2虚表中func1均被Derive重写,func3存放在base1中。
但为什么重写后的fun1在两张虚表中的地址为什么不同呢?
观察上图,可以发现基类Base1指针ptr1和派生类Derive指针ptr3,虽然类型不同,但指向的都是同一个地址(Derive的首地址),所以ptr1可以直接访问func1。而Base2类型的指针ptr2指向的地址与ptr1不同,不能直接访问func1,所以就需要在访问fun1之前修正ptr2(减去base1的大小)的地址,使得ptr2和ptr1指向的地址相同。综上,就可以说明为什么两张虚表中func1的地址不同,原因就是让ptr2在调用func1之前去另一个地址(0062134D / base虚表中func1的地址),在底层做一些处理,即先去修正ptr2的地址。
所以说虽然两个func1的地址不同,但都是调用同一个函数func1。
(在内存中不看类型,只看数据是否相同)