现在的位置: 首页 > 综合 > 正文

Q13.3 关于虚表指针、虚函数表、虚函数表中虚函数地址

2018年04月29日 ⁄ 综合 ⁄ 共 2131字 ⁄ 字号 评论关闭

本文主要学习于 http://blog.csdn.net/haoel/article/details/1948051/ ,另添加一部分自己的理解。


        先说一个小白问题,我这才想明白。why用int数组表示地址数组(指针数组),里边存的都是int代表地址?

        因为 sizeof(int) = sizeof(int*) = sizeof(char*) = sizeof(float*) = .... = 4。需要找地址的时候加个转型就可以得到地址,如果是char数组,一个 char里边存的数据不足4字节,而地址都为4字节,强转型后数组不足以表示地址。


下边言归正传:

        虚函数依赖虚函数表进行工作。如果一个类中,有函数被关键词virtual进行修饰, 那么一个虚函数表就会被构建起来保存这个类中虚函数的地址。同时, 编译器会为这个类添加一个隐藏指针指向虚函数表。如果在派生类中没有重写虚函数, 那么,派生类中虚表存储的是父类虚函数的地址。每当虚函数被调用时, 虚表会决定具体去调用哪个函数。因此,C++中的动态绑定是通过虚函数表机制进行的。

        当我们用基类指针指向派生类时,虚表指针vptr指向派生类的虚函数表。 这个机制可以保证派生类中的虚函数被调用到。


什么是虚函数表

    虚函数(Virtual Function)是通过一张虚函数表(Virtual
Table
)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

      C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

    虚表指针顾名思义就是指向虚函数表的指针。

    下附一个小程序验证一下上面的说法,并详细附注。

<span style="font-size:14px;">#include <iostream>
using namespace std;

class Base
{
	virtual void a()
	{
		cout << "Base::a()" << endl;
	}
	virtual void b()
	{
		cout << "Base::b()" << endl;
	}
	virtual void c()
	{
		cout << "Base::c()" << endl;
	}
};

class Derived : public Base
{
	virtual void a()
	{
		cout << "Derived::a()" << endl;
	}
	virtual void d()
	{
		cout << "Derived::d()" << endl;
	}
};

int main(void)
{
	typedef void(*func)(void);  //申请一个最基础的函数指针 
	
	Base t;
	func pfunc = 0;
	
	cout << "虚函数表地址: " << (int*)(&t) << endl;         
	int *p = (int*)(&t);  
    
	cout << "虚函数表-第一个函数地址:" <<(int*)*p << " " << (int*)p[0] << endl;
	// p是虚表指针,指向虚函数表。于是*p或者p[0]就代表虚函数表的第一组数据,因为这组数据实际是虚函数的地址,
	// 因此转型为指针(int*)*p或者(int*)p[0]均意为第一个虚函数的地址。
	
	int *q = (int*) *p;   //q为指向虚函数表里对应函数 “数据”的指针, 由于是函数,“数据”仍然是地址。 
	                      //PS:函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址
	
	pfunc = func (*q);    //*q为那个函数,pfunc指向该函数; 
	pfunc();  
	
	//若要找实例的第二、三个虚函数,只需要把q后移 !!注意不是后移p,后移p就跑去类实例的其他部分了。 
	
	pfunc =          (func) *( (int*) *(int*)(&t) +1 );
	//pfunc =          (func) *( (int*) *p +1 );
	//pfunc = (func) *(q + 1);
					 //(Fun) *( (int*) *(int*)(&b)+1);  // Base::g()
	pfunc();            
	
	pfunc = (func)*((int*)*(int*)(&t)+2);
	pfunc();
	
	Derived d;           //派生类实例化 
	
	pfunc = (func) *((int*)*(int*)(&d)); //由于派生类重载了基类的 a函数,因此虚函数列表中
										 // a函数的地址已经被更新为派生类的版本。
										 //于是在实际调用中,实现了多态。 
	pfunc();
	
	pfunc = (func) *((int*)*(int*)(&d) + 3);   //派生类自有的函数(如本例中的d函数)会在虚函数列表的尾端添加。 
	pfunc();
	
	return 0;
}</span>


插几个个图,简单明了。

1、仅基类 的 虚函数表


2、派生类重载基类函数 的 虚函数表


3、多重继承(无重载)中的虚函数表


4、多重继承(有重载)中的虚函数表


抱歉!评论已关闭.