首先来看一段代码:
这是一个经典的C++多态问题。答案正如你出乎预料的那样,输出Hello。#include <iostream> using namespace std; class IHello{ public: virtual void Hello()= 0; }; class IWorld{ public: virtual void World()= 0; }; class HelloWorld:public IHello, public IWorld{ public: virtual void Hello(){ cout<<"Hello"<<endl; } virtual void World(){ cout<<"World"<<endl; } }; int main(){ IHello* hello = new HelloWorld; IWorld* world = (IWorld*)(void*)hello; world->World(); }
对此,前辈的解释为“这是面向对象中经常会使用的一种使程序呈现多态性的手段,也就是动态联编,只有运行时才知道具体执行的代码,这里面的子对象HelloWorld在生成对象时会从对象初始地址开始建立一个虚函数表,用来保存虚函数的地址,也即第一个地址指向Hello(),第二个地址保存World(),继承时,HelloWorld对象的虚函数表指针则将相应的指针指向其覆盖的函数,但是虚函数地址的相对位置还是Hello()在前,World()在后,在用IWorld进行强转之后,world指向了HelloWorld的入口虚函数地址,当执行world->World()时实际上是转去执行Hello函数去了。”
对这段话的理解还是不甚清楚。于是写了这样一段代码进行测试。
int main() { HelloWorld* aa =new HelloWorld; IWorld* world1 = (IWorld*)aa; world1->World(); }
输出为world.按照上面的解释,world1指针执行world()函数时,会从虚函数表中找第一个函数地址,所以执行结果也应该是hello才对。那问题在哪里呢?
于是又写了一段代码来进行测试:
这次的执行结果,没错正是hello。int main(){ HelloWorld* aa =new HelloWorld; IWorld* world1 = (IWorld*)(void*)aa; world1->World(); }
两次的执行结果迥然不同,只因为加了一次void*强转,那么对编译器的影响是什么呢?
不妨看看内存是怎么样的。执行第一段代码时,查看到的内存如下
可以看到world1指针的指并不等于aa,而是向后偏移了4个字节,这样就正好指向了world()函数的地址。
而第二段代码的内存如下
这次两个指针的值是一样的,所以也就产生了上述结果。
于是我们有这样的猜想,当将一个子类的指针强转为父类的指针时,会根据其父类对象在子类对象中的位置产生相应的偏移。而当你强转成void*指针之后,由于无法识别指针的类型,所以就只能直接赋值了