明确以下几点:
多态的实现是依靠虚函数表,程序需要额外的查询虚函数表的开销。
C++的构造函数中可以调用虚函数,说明虚函数表的产生是在构造函数调用之前。虚函数表的建立是在编译时(Compile-Time)。
看一个例子:
#include <iostream> using namespace std; class A { int m1; int m2; public: A() { fun2(); } virtual void fun2() { cout<<"call A::fun2 success!"<<endl; } }; class B:public A { public: B() { fun2(); } void fun2() { cout<<"call B::fun2 success!"<<endl; } }; int main() { B *pa = new B(); return 1; }
输出结果:
call A::fun2 success!
call B::fun2 success!
说明两个构造函数内的虚函数都调用成功。由于对象的构造顺序和子类对基类虚函数的重载还未完成,所以第一个fun函数是调用的A的虚函数。
(在JAVA中会输出两个call B::fun2 success!,WHY??)
虚函数表的地址vptr是存在对象的this指针指向的结构体的第一个数据位置,即存储vptr的地址和this的地址值相同。
一个类的所有对象都共享同一份虚函数表。
看一个例子:
#include <iostream> using namespace std; class A { int m1; int m2; public: virtual void fun2() { cout<<"call A::fun2 success!"<<endl; } void fun() { cout<<this<<' '<<&m1<<' '<<hex<<*((int*)this)<<endl; //*((int*)this)取vptr的值 } }; int main() { A *pa1 = new A(); A *pa2 = new A(); pa1->fun(); pa2->fun(); return 1; }
输出结果:
00332BA0 00332BA4 46F01C
00332BD8 00332BDC 46F01C
两个对象的this值不同,数据成员的地址也不同,但是其中的vptr值是相同的。
5. 虽然子类重载了基类的虚函数,但是通过子类对象依然可以访问到基类的虚函数。
看例子:
#include <iostream> using namespace std; class A { int m1; int m2; public: virtual void fun2() { cout<<"call A::fun2 success!"<<endl; } }; class B : public A { public: virtual void fun2() { cout<<"call B::fun2 success!"<<endl; } }; int main() { A *pa = new B(); pa->A::fun2(); return 1; }
输出结果:
call A::fun2() success!
也就是说,在B的虚函数表中,依然存在着A的虚函数的入口地址,而且是可以调用的。