15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include
#include
using std::cout;
using std::endl;
structAnimal{voidmakeSound(){cout<<"动物叫了"< structCow:publicAnimal{voidmakeSound(){cout<<"牛叫了"< structPig:publicAnimal{voidmakeSound(){cout<<"猪叫了"< structDonkey:publicAnimal{voidmakeSound(){cout<<"驴叫了"< intmain(intargc,constchar*argv[]) { srand((unsigned)time(0)); intcount=4; while(count--){ Animal *animal=nullptr; switch(rand()%3){ case0: animal=newCow; break; case1: animal=newPig; break; case2: animal=newDonkey; break; } animal->makeSound(); delete animal; } return0; } 程序中有一个基类Animal,它有一个makeSound()函数。有三个继承自Animal的子类,分别是牛、猪、驴,并且实现了自己的makeSound()方法。很简单的代码,是吧。 我们运行程序,你觉得输出结果会是什么呢?不错,这里会连续执行4次Animal的makeSound()方法,结果如下: 为什么?因为我们的基类Animal的makeSound()方法 没有使用Virtual修饰,所以在静态编译时就makeSound()的实现就定死了。调用makeSound()方法时,编译器发现这是Animal指针,就会直接jump到makeSound()的代码段地址进行调用。 ok,那么我们把Animal的makeSound()改为虚函数,如下: 1 2 3 4 5 6 structAnimal{ virtual voidmakeSound() { cout<<"动物叫了"< } }; 运行会是怎样?如你所料,多态已经成功实现: 接下来就是大家最关心的部分,这是怎么回事?编译器到底做了什么? 虚表 为了说明方便,我们需要修改一下 基类Animal的代码,不改变其他子类,修改如下: 1 2 3 4 5 6 7 8 9 structAnimal{ virtual voidmakeSound(){cout<<"动物叫了"< virtual voidwalk(){} voidsleep(){} }; structCow:publicAnimal{voidmakeSound(){cout<<"牛叫了"< structPig:publicAnimal{voidmakeSound(){cout<<"猪叫了"< structDonkey:publicAnimal{voidmakeSound(){cout<<"驴叫了"< 首先我们需要知道几个关键点: 函数只要有virtual,我们就需要把它添加进vTable。每个类(而不是类实例)都有自己的虚表,因此vTable就变成了vTables。虚表存放的位置一般存放在模块的常量段中,从始至终都只有一份。详情可在此参考 我们怎么理解?从本例来看,我们的Animal、Cow、Pig、Donkey类都有自己的虚表,并且虚表里都有两个地址指针指向makeSound()和walk()的函数地址。一个指针4个字节,因此每个vTable的大小都是8个字节。如图: 他们的虚表中记录着不同函数的地址值。可以看到Cow、Pig、Donkey 重写了makeSound()函数但是没有重写walk()函数。因此在调用makeSound()时,就会直接jump到自己实现的code Address。而调用walk()时,则会jump到Animal父类walk的Code Address。 虚指针 现在我们已经知道虚表的数据结构了,那么我们在堆里实例化类对象时是怎么样调用到相应的函数的呢?这就要借助到虚指针了(vPointer)。 虚指针是类实例对象指向虚表的指针,存在于对象头部,大小为4个字节,比如我们的Donkey类的实例化对象数据结构就如下: 我们修改main函数里的代码,如下: 1 2 3 4 5 6 7 8 9 10 intmain(intargc,constchar*argv[]) { intcount=2; while(count--){ Animal *animal=newDonkey; animal->makeSound(); delete animal; } return0; } 我们在堆中生成了两个Donkey实例,运行结果如下: 1 2 3 驴叫了 驴叫了 Program ended with exit code:0 没问题。然后我们再来看看堆里的结构,就变成了这样: 还有什么是这张图不能说明的呢? Enjoy it :) 参考链接: C++ vTable PreviewC++ vPointers and vTablesVirtual method table虚函数表放在哪里返回搜狐,查看更多