C++中的虚函数的作用主要是实现了多态的机制。通过虚函数,实现函数调用时的动态绑定,如果调用虚函数,
则直到运行时才能确定调用了哪个函数。
C++的虚函数通过一张虚函数表来实现,主要是一个类虚函数的地址表,通过这张表,解决了继承、覆盖的问题。
注意:申请了一个类对象后,占用的地址空间的大小和类的成员函数多少无关,函数都是存放在代码区的,
不管是全局函数,还是成员函数。(static 函数也是放着代码区,不是放在全局数据区)
C++的编译器暴走虚函数指针存在于对象实例中最前面的位置。所以,我们只要找到存储对象的起始地址,就能找到找到
相关的虚函数表地址,从而能逐个的访问每个虚函数。
例如定义一个基类:
class Base{
int a;
public:virtual void f(){std::cout <<"Base::f()"<<std::endl;}virtual void g(){std::cout<<"Base::g()"<<std::endl;}void k(){std::cout<<"novirtual function , k()"<<std::endl;}virtual void h(){std::cout<<"Base::h()"<<std::endl;}
};
我们可以通过下面的代码做一些测试:
int main()
{
typedef void(*Fun)(void);
Fun fun=NULL;
Base b;
cout<<&b<<endl;
cout<<sizeof(b)<<endl;
cout<<&b.a<<endl;
cout<<sizeof(b.a)<<endl;
return 0;
}
打印结果如下:
0x28fef4 8 0x28fef84
分析:(注意sizeof访问的程序的数据段) 通过打印结果,可以看出,b实例分配了8个字节的内存,
相当于两个int的大小,第一个b的一个成员变量a储存在后4个字节空间,前4个字节就是用来存放
虚函数表地址的,因此我们可以通过这个地址来获得虚函数地址。
(int*)(&b) 表示虚函数表地址指针==》*(int*)(&b)虚函数表指针==》
(int*)*((int *)*(int*)(&b))第一个虚函数地址。下面进行验证:
cout<<(int *)*((int*)*((int *)(&b)))<<endl;
fun=(Fun)*((int*)*((int *)(&b)));
fun();
在开始代码后加入上面代码,运行打印结果为:
0x4201e0
Base::f()
根据上面的分析(int*)*((int *)*(int*)(&b)+1)应该是第二个虚函数地址,
(int*)*((int *)*(int*)(&b)+2)应该是第3个虚函数地址。打印可以验证。
fun=(Fun)*((int*)*((int *)(&b))+1);
fun();
fun=(Fun)*((int*)*((int *)(&b))+2);
fun();
打印结果为:
Base::g()
Base::h()
如图:
进行继承后:
class Drive:public Base{
public:
virtual void f1(){std::cout<<"Drive1::f1()"<<std::endl;}
void g(){std::cout<<"Drive1::g()"<<std::endl;}
virtual void h1(){std::cout<<"Drive1::h1()"<<std::endl;}
};
在上面继承中 成员函数g()覆盖了Base的虚函数,f1()和h1为继承类新的虚函数,
内存结构如下图:
若为多重继承时,子类中将为每个父类维护一张虚函数表,子类中的成员虚函数被放到第一
个父类的虚函数表中。有覆盖时,将相应表格中相同的虚函数进行覆盖。
(可以按照最初的方法进行验证)