虚指针、虚表

我们都知道虚函数是c++实现多态性的体现,虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

我们来看一段代码:

#include<iostream>
using namespace std;
class A
{
public:
	A(int a1)
	{
		a = a1;
	}
	virtual void fun1()
	{
		cout << "基类虚函数" << endl;
	}
	virtual void fun2()
	{

	}
private:
	int a;
};
class B :public A
{
public:
	B(int a1, int b1) :A(a1)
	{
		b = b1;
	}
	virtual void fun1()
	{
		cout << "派生类虚函数" << endl;
	}
	virtual void fun2()
	{

	}
private:
	int b;
};
void print1(A *p)
{
	p->fun1();
}
void print2(A &p)
{
	p.fun1();
}
void print3(A p)
{
	p.fun1();
}
int main()
{
	A aa(10);
	B bb(20, 30);
	aa.fun1();
	bb.fun1();
	print1(&aa);
	print1(&bb);
	print2(aa);
	print2(bb);
	print3(aa);
	print3(bb);
	system("pause");
	return 0;

}

其实不难看出,程序运行结果如下:

print3(bb);并不是通过基类指针或引用来传参,所以调用的是基类的fun1()。

那么sizeof(aa)的大小是多少呢? 答案是8,包括一个整型变量a和一个虚指针。

这个时候我们就引入虚指针的概念,我们在vs编译器上调用监视窗口

我们可以看到对象aa中不仅有私有成员a,还有一个vfptr指针,这个指针就是虚指针,指向的是一个虚表,虚表中存放得是类中所有虚函数的地址。

我们可以看到,vfptr指向一个虚表,虚表中存放着fun1()和fun2()函数的地址。

如下图所示:

虚函数表最后一个元素是0.

我们可以通过下面的代码来打印虚表:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "A::fun2()" << endl;
	}
};
class B :public A
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
};
typedef void (*FUNC)();

void printVtable(int *vfptr)
{
	cout << "虚表地址:" << vfptr << endl;
	for (int i = 0; vfptr[i] != 0; i++)   //虚表最后一个元素是0
	{
		printf("第%d个虚函数地址:0x%x,->", i, vfptr[i]);
		FUNC f = (FUNC)vfptr[i];
		f(); //调用该虚函数
	}
	cout << endl;
}

int main()
{
	A a;
	B b;
	int *vfptr1 = (int*)(*(int*)(&a));
	int *vfptr2 = (int*)(*(int*)(&b));
	printVtable(vfptr1);
	printVtable(vfptr2);
	system("pause");
	return 0;
}

虚指针vfptr需要我们自己通过对象的地址来找到,虚指针的地址是对象地址的前四个字节即 (int*)(&a),然后再将其解引用即为vfptr ,但是虚指针是int * 类型,所以我们需要在把其强转成int * 即为(int *)(*(int*)(&a))。

程序运行结果:

我们可以看得出来,基类对象a有一个虚指针,指向的是基类的虚表,存放基类的虚函数地址。派生类对象b同样拥有自己的虚指针,指向的是派生类的虚表,存放的是派生类全部虚函数地址。上述情况是单继承,那么如果有一个类同时继承两个类,那么虚指针和虚表又是如何的呢? 我们继续看下一段代码:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "A::fun2()" << endl;
	}
protected:
	int a;

};
class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
protected:
	int b;
};
class C :public A, public B
{
public:
	virtual void fun1()
	{
		cout << "C::fun1()" << endl;
	}

	virtual void fun3()
	{
		cout << "C::fun3()" << endl;
	}
protected:
	int c;

};
typedef void(*FUNC)(); //声明一个函数指针类型FUNC

void printTable(int *vfptr)
{
	cout << "虚表地址:" << vfptr << endl;
	for (int i = 0; vfptr[i] != 0; i++)
	{
		printf("第%d个虚函数地址:%p->", i, vfptr[i]);
		FUNC f = (FUNC)(vfptr[i]);
		f();  //调用这个函数
	}
	cout << endl;
}
int main()
{
	C c;
	cout << sizeof(c) << endl;
	int *vfptr1 = (int*)(*((int*)(&c)));
	printTable(vfptr1);
	int *vfptr2 = (int*)(*(int*)(((char*)(&c)) + sizeof(A)));
	printTable(vfptr2);
	system("pause");
	return 0;

}

程序运行结果:

我们看到对象c的大小为20字节,原因是继承父类的两个整型,自己的整型变量,还有两个虚指针,8+4+8=20.。而且第二个虚表指针存放在第一个父类全部成员的下面,如图:

知道虚指针的存放位置后,我们就可以将第二个虚指针表示出来,第二个虚表指针在&c后需要加上第一个父类的大小,但是由于指针+1,加的是所指类型的大小,所以需要把&a强转为char*,或者+sizeof(A)/4,
    int *vfptr2 = (int*)(*(int*)(((char*)(&c)) + sizeof(A)));

通过程序运行结果我们可以得出结论,第一个虚指针指向一个继承父类A的虚表,里面存放的是派生类虚函数地址,需要我们注意的一点是,fun3()是派生类自己定义的虚函数,而不是重写父类的,所以它存放在第一个虚表后面。同样,第二个虚表指针也指向一个继承父类B的虚表,存放着虚函数地址(除了派生类自己定义的虚函数fun3())。我们可以通过fun2()来判断,虚指针到底指向的是继承A还是B的虚表。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值