下面的例子如果你能很快说出答案,说明你的理解还是很到位的,那么下面的讲解就不需要浪费时间看了。
请看如下代码:
#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;
}
总结:
这是在没有继承情况下的虚函数分析,对于指针的理解决定了对虚函数的理解,虚函数本身其实没有什么好说的,无非多生成了个虚函数表,然后在虚函数表里面保存你的函数,接下来就是用指针来调用罢了。