C++的虚函数的作用就是为了实现多态的机制,利用内存的指针偏移来实现将基类型的指针指向的内存空间用子类对象来初始化。这样经过内部虚表的运作,实现可以通过基类指针来调用子类所定义的方法。
这种技术,其实就是一种泛型技术,试图让不变的代码来实现可变的算法。比如:模板、RTTI、虚函数。实现在编译时决定方法,或者就是在运行时决定方法。
虚函数表:
简称vtbl,这个表中保存了一个类的虚函数地址,这张表解决了继承、覆盖的问题。
在有虚函数的类对象中这个表被分配在对象的内存空间中。创建虚表,赋值地址的工作,其实都是由编译器来完成,编译器通过自动添加代码到构造函数中来实现。
C++的编译器应该保证虚函数表的指针存在于对象实例内存中最前面的位置。既然是首地址,那么当我们获取一个对象的首地址时,其实也就是得到了这个对象所使用的虚表指针的地址,于是也就可以顺利的得到虚表的地址。
1 class Base 2 { 3 public: 4 virtual void f() {cout << "Base::f()" << endl; }; 5 virtual void g() {cout << "Base::g()" << endl; }; 6 virtual void h() {cout << "Base::h()" << endl; }; 7 };
// 按照上面的类声明,我们来创建一个对象 // 然后得到这个对象所使用的虚表 Base b; typedef void (*Fun)(void); Fun pFun = NULL; cout << "虚函数表地址" << (int*)(&b) << endl; // 其实就是虚指针vbtr的值,因为vbtr的地址和对象的地址相同 cout << "虚函数表: 第一个函数地址" << (int*)*(int*)(&b) << endl; pFun = (Fun)*((int*)*(int*)(&b)); pFun();
运行结果:
然后通过: pFun = (Fun)* ((int*)*(int*)(&b) + 1);取得第二个函数, pFun = (Fun)* ((int*)*(int*)(&b) + 2); 取得第三个函数;
以此类推....
一般继承(无虚函数覆盖):
通过一个子类Derive继承基类,但不做虚函数覆盖,于是子类的虚表如下所示:
1 class Derive : public Base 2 { 3 public: 4 virtual void f() {cout << "Derive::f()" << endl; }; 5 virtual void g() {cout << "Derive::g()" << endl; }; 6 virtual void h() {cout << "Derive::h()" << endl; }; 7 virtual void f1() {cout << "Derive::f1()" << endl; }; 8 virtual void g1() {cout << "Derive::g1()" << endl; }; 9 virtual void h1() {cout << "Derive::h1()" << endl; }; 10 protected: 11 private: 12 };
子类虚函数表:***Base::f()地址***Base::g()地址***Base::h()地址***Derive::f1()地址***Derive::g1()地址***Derive::h1()地址***
作为子类对象,它的虚表和Base类的虚表不是同一个表,子类的虚函数如果不实现,则以此排列。
一般继承(有虚函数覆盖):
1 class Derive : public Base 2 { 3 public: 4 void f() {cout << "Derive::f()" << endl; }; 5 protected: 6 private: 7 };
此时Derive的虚表,第1个函数已经指向了子类的实现。
多重继承(无虚函数覆盖):
每个父类都有自己的虚表,但子类成员函数被放在了第一个声明基类的虚表中。
多重继承(有虚函数覆盖):
三个基类表中的f()位置都被替换成了子类的函数指针