对C++虚函数的理解(一)

下面的例子如果你能很快说出答案,说明你的理解还是很到位的,那么下面的讲解就不需要浪费时间看了。
请看如下代码:

#include<iostream>
using namespace std;
class Base
{
public:
	virtual void foo1()
	{
		cout << "virtual void foo1()" << endl;
	}
	virtual void foo2()
	{
		cout << "virtual void foo2()" << endl;
	}
	virtual void foo3()
	{
		cout << "virtual void foo3()" << endl;
	}
};
int main()
{
	Base *b = new Base;
	((void(*)())(*(int**)(*(int*)b)))();
    ((void(*)())(*((int**)(*(int*)b) + 1)))();
	((void(*)())(*((int**)(*(int*)b) + 2)))();

	//C++加长版(没啥区别和上面,吓唬人用用)
	cout << "------------------------------" << endl;
	(reinterpret_cast<void(*)()>(*(reinterpret_cast<int**>(*(reinterpret_cast<int*>(b))))))();
	(reinterpret_cast<void(*)()>(*((reinterpret_cast<int**>(*(reinterpret_cast<int*>(b)))) + 1)))();
	(reinterpret_cast<void(*)()>(*((reinterpret_cast<int**>(*(reinterpret_cast<int*>(b)))) + 2)))();

	while (1);
	return 0;
}

输出:
在这里插入图片描述
分析:
首先Base *b = new Base;
因为有虚函数的存在,所以编译器为我们合成一个默认的构造函数出来,在默认的构造函数中,会插入虚函数表的代码,并将表的地址用一个指针保存起来。
所以如果用sizeof这个关键字去看类Base的大小,得到的答案是4。
相当于:
base()
{
/*创建虚函数表的代码/

vtable* p;//有一个虚函数表的指针被自动创建了.
(这个虚函数表的类型就相当于一个指针数组)
/*所以可以等同于:int** p(或者char**p),这个int和char在我的这个代码里其实无所谓,他们都是用一个指针来保存函数的地址的,所以大小都是固定的,或者写成short**p都无所谓,读者不妨将我的代码的这一部分进行更换再试试看*/
比如:
	((void(*)())(*(char**)(*(int*)b)))();
	((void(*)())(*((char**)(*(int*)b) + 1)))();
	((void(*)())(*((char**)(*(int*)b) + 2)))();
	cout<<"----------------------------"<<endl;
    ((void(*)())(*(long**)(*(int*)b)))();
	((void(*)())(*((long**)(*(int*)b) + 1)))();
	((void(*)())(*((long**)(*(int*)b) + 2)))();
}

以此类为例,只要是该类中声明的虚函数,都会以指针的形式保存到虚函数表中去,以备需要的时候被调用。

所以当我new完这个类的时候,接下来如果我要调用我想要的函数,所做的的工作无非是找到在这个类中虚函数表的地址而已。

那么问题来了,这个虚函数表的地址在哪里?
//这两行为了便于理解,换个名字也许更好理解些。
typedef int** Vtable;
typedef int* Base_ChengYuan;

接下来请看代码:

    Base *b = new Base;
	cout <<" 类Base的地址:"<< b << endl;
	cout << "虚函数表的地址:" << (Vtable)(*(Base_ChengYuan)b) << endl;
	cout << "虚函数表首元素的地址:" << *(Vtable)(*(Base_ChengYuan)b) << endl;
	cout << "虚函数表第一个元素的地址:" << *((Vtable)(*(Base_ChengYuan)b)+1) << endl;
	cout << "虚函数表第二个素的地址:" << *((Vtable)(*(Base_ChengYuan)b) + 2) << endl;

输出:
在这里插入图片描述对应下面的图:
在这里插入图片描述
接下来我把我的代码进行分解,请看:

    //   Base *b = new Base;
	//  (Base_ChengYuan)b    把指针b的类型转化为类Base成员的类型(以便格式化的需要)
	//  *(Base_ChengYuan)b   以四个字节为大小取内容
	//  (Vtable)(*(Base_ChengYuan)b)  取得的内容实际为虚函数表,他是一个地址,必 须显示的转化其类型
	//  *((Vtable)(*(Base_ChengYuan)b))  虚函数表的内容是一个指针,且为函数指针 
	//  (*((Vtable)(*(Base_ChengYuan)b)))();  知道是函数指针了,接下来就可以调用了

至此,一切的迷雾便已经解开。

看看你是否真的理解了,请解释如下代码:

    ((void(*)())(((int*)(*((int*)b)))[0]))();
	((void(*)())(((int*)(*((int*)b)))[1]))();
	((void(*)())(((int*)(*((int*)b)))[2]))();
在这里插入代码片

输出:
在这里插入图片描述
本质和前面的代码没有什么不同,换了一种形式解释罢了。

思考:
这样子行吗?

((void(*)())((int*)(*((int*)b))))();

类比

#include<iostream>
using namespace std;
typedef void(*FUNC)();
void foo()
{
	cout << "void foo()" << endl;
}
void func()
{
	cout << "void func()" << endl;
}
int main()
{

	int arr[2] = {(int)foo,(int)func};

	//((void(*)())arr)();
	((void(*)())(arr[0]))();
	((void(*)())(arr[1]))();

while(1);
return 0;
}

总结:
这是在没有继承情况下的虚函数分析,对于指针的理解决定了对虚函数的理解,虚函数本身其实没有什么好说的,无非多生成了个虚函数表,然后在虚函数表里面保存你的函数,接下来就是用指针来调用罢了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coder_gaozhiwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值