多继承的虚函数表
#include <iostream> using namespace std; 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; }; typedef void(*VFPTR) (); typedef void(*p)(); void PrintVFTbale(p vft[])//打印虚表 { for (int i = 0; vft[i] != nullptr; i++) { printf("[%d]:%p->", i, vft[i]); vft[i](); } } int main() { Base1 b1; Base2 b2; PrintVFTbale((p*)(*(void**)&b1)); PrintVFTbale((p*)(*(void**)&b2)); Derive d; PrintVFTbale((p*)(*(void**)&d));//d有两个虚表 PrintVFTbale((p*)(*(void**)((char*)/*不强转就不是+1跳过一个字节了*/ &d + sizeof(Base1))));//想要打印第二个虚表就要跳过第一个虚表的地址 return 0; }
我们发现,d中的func3虚函数放进了第一个虚表里面。
菱形虚拟继承
#include<iostream> using namespace std; class A { public: virtual void func1(){} int _a; }; class B : public A //class B : virtual public A { public: virtual void func1() {} int _b; }; class C : public A //class C : virtual public A { public: virtual void func1() {} int _c; }; class D : public B, public C { public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
如果是菱形继承,这样重写虚函数是没有问题的,因为B和C都有一个A。
但如果是菱形虚拟继承就不可以了,因为A类变成了一个,B和C进行重写A类的函数的时候编译器无法分辨应该按照B还是C进行重写。
所以这种情况只能在d中进行重写。
在d中重写之后,d就有三张虚表,分别是A,B,C。
#include<iostream> using namespace std; class A { public: A(const char* s) { cout << s << endl; } ~A() {} }; class B :virtual public A { public: B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; } }; class C :virtual public A { public: C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; } }; class D :public B, public C { public: D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2),C(s1, s3),A(s1) { cout << s4 << endl; } }; int main() { D* p = new D("class A", "class B", "class C", "class D"); delete p; return 0; }
这里要注意:
1.因为是菱形虚拟继承,A只有一份,定义D是直走D当中的初始化列表的A类初始化,不会走B和C类的A类初始化,因为A是B和C共享的,B和C去初始化都不合适,所以A只被D初始化了一次。
但是B和C类中的A类初始化必须有,因为万一要创建B和C类就需要有A类的初始化了。
2.初始换是按照成员声明顺序初始化(如果是继承就看继承顺序),而不是看初始化列表的顺序进行初始化。
那么如果只是菱形继承呢?
#include<iostream> using namespace std; class A { public: A(const char* s) { cout << s << endl; } ~A() {} }; class B :public A { public: B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; } }; class C :public A { public: C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; } }; class D :public B, public C { public: D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2),C(s1, s3)//这里就不用初始化A了,因为有两份A,在B和C中,B和C就会去调用A的初始化 { cout << s4 << endl; } }; int main() { D* p = new D("class A", "class B", "class C", "class D"); delete p; return 0; }
继承与多态的常见问题
1.什么是多态?
静态的多态和动态的多态。就是重载和虚函数的重写。
2.内联函数能不能是虚函数呢?
语法上来说是可以的,但是其实是并不行的,因为inline关键字只是提出一个建议,到底需不需要去变成内联函数是要看实际情况的,如果将虚函数变成内联函数是没有办法放进虚表里面的,编译器就忽略inline属性,这个函数就不再是inline。
也就是说,如果是多态调用就没有内联属性,但是普通调用可以继续保持内联属性。
3.为什么父类的对象无法完成多态调用呢?
#include <iostream> using namespace std; class A { public: virtual void add() { cout << "A:add" << endl; } int _a; }; class B :public A { public: virtual void add() { cout << "B:add" << endl; } int _b; }; void app(A a)//这里传过来的如果是B类的对象,顶多是切片过来的A类一部分,不可能是拷贝过来所有的内容 { a.add();//这里就是静态绑定,普通调用 } int main() { A a; B b; app(a); app(b);//如果拷贝到A a中,里面的内容就乱套了,所以只能用引用或者是指针才能指向子类对象完成多态的调用 return 0; }
4.静态成员可以是虚函数吗?
不可以,因为静态成员函数没有this指针(this指针指向的对象中存有虚函数表指针,没有this指针就找不到对象,也就找不到虚函数表指针了),使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
5.构造函数能不能是虚函数?
虚函数要放进虚表,在没有调用构造函数之前,虚表还有没进行初始化,构造函数之后才进行初始化。
这也说明,如果构造函数都是虚函数,那么我们就无法找到构造函数的地址了。
6.对象访问普通函数快还是虚函数更快?
首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
7.虚函数表是在什么阶段生成的,存在哪的?
虚函数表是在编译阶段就生成的初始化列表中初始化虚表指针,一般情况下存在代码段(常量区)的。