1、没有任何数据成员和成员函数的空类
#include <iostream.h> #include <string.h> class A { }; void main(void) { cout << sizeof(A) << endl; // 1 ,为什么是1,不懂啊 return; }
2、为类A添加一个非虚函数
#include <iostream.h> #include <string.h> class A { public: int add(int i) { return ++i; } }; void main(void) { cout << sizeof(A) << endl; //与空类A一样,还是1 return; }
3、为类A加入虚函数
#include <iostream.h> #include <string.h> class A { public: virtual int add(int i) { return ++i; } virtual int sub(int i) { return --i; } }; void main(void) { cout << sizeof(A) << endl; //输出4,解释见下面 return; }
虚函数表的指针4个字节大小(vptr),存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说,虚函数表的每一项是一个虚函数的指针。
没有虚函数的C++类,是不会有虚函数表的。
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。 如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
在继续进行之前,先来看一下简单的东西
void main(void) { class S { public: int k; int m; }; S a; a.k = 5; a.m = 4; cout<<*((int *)&a)<<endl; //5, 就是输出a.k cout<<*((int *)&a+1)<<endl; //4, 就是输出a.m return; }
其实亮点就是最后的两个输出语句 cout<<*((int *)&a)<<endl;下面对其分解
(1) class S *ps = &a, 取得对象a的地址
(2) int *pi= (int *)&a,将S型的指针转化为int型的指针,现在pi指向了对象a前四个字节的部分(int占四个字节),也就是int k
(3) *pi也就等于5啦。
再来最终版的,理解啦下面的程序也就理解了虚函数表
#include <iostream.h> #include <string.h> class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; typedef void(* FunType)(void); int main(void) { Base b; FunType fun; cout << "对象b的地址,也是指向虚函数表的指针的地址:" << &b << endl; //编译器应该是保证指向虚函数表的指针存在于对象实例中最前面的位置 //(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下) cout << "虚函数表的地址:" << *(int*)(&b) << endl; //我的机器上输出4362492,转换成16进制就是0x004290FC cout << "虚函数表的地址:" << (int *)*(int*)(&b) << endl; //我的机器上输出0x004290FC // *(int*)(&b) 的值就是一个整数,表示的是虚函数表的地址 // 前面在加上一个(int)*,其实就是将一个整数指针话了而已,例如 int *p = (int *)1024; cout << "虚函数表中第一个虚函数的地址:" << *(int *)*(int*)(&b) << endl; //10进制形式 cout << "虚函数表中第一个虚函数的地址:" << (int *)*(int *)*(int*)(&b) << endl; //16进制形式 //先来一个的 fun = (FunType)*(int *)*(int*)(&b); fun(); //再来一群的 for(int i=0; i<3; i++) { fun = (FunType)*((int *)*(int*)(&b)+i); fun(); } return 0; }
最后做到题目,联系一下(嘿嘿,做点坏事情,访问基类的私有虚函数)
#include <iostream.h> class A { virtual void g() { cout << "A::g" << endl; } private: virtual void f() { cout << "A::f" << endl; } }; class B : public A { void g() { cout << "B::g" << endl; } virtual void h() { cout << "B::h" << endl; } }; typedef void( *Fun )( void ); void main() { B b; Fun pFun; for(int i = 0 ; i < 3; i++) { pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); pFun(); } } //输出结果: B::g A::f B::h