C++中,存在特殊的类型/子类型关系,基类指针或引用可以直接引用其任意派生的子类,无需程序员介入。这种“用基类的指针或引用操纵多个类型”的能力就叫做多态,即“基类指针有多种形态”。
C++中的虚函数的作用主要是实现了多态的机制。什么又叫做虚函数呢?之所以被称为虚函数,是因为此类函数在被调用之前都不会确定会被谁调用。需要在运行的时候解析出被调用的函数,这个解析过程被称为动态绑定,又叫延迟绑定。在C++中,正是通过一种被称为虚拟函数的机制来支持动态绑定的。虚函数可以通过相同的函数实现不同的功能。这便是虚函数的特点。
虚函数是通过一张虚函数表来实现的。虚函数表其实不难,只是不容易说清楚。
首先,如果类定义中有虚函数,那么其类的实例中则有虚函数表。这个虚函数表仅仅是一个静态数组,由编译器在编译的时候生成。虚函数表中包含的是各个虚函数的入口地址,这些虚函数可以被类的对象调用。
其次,编译器会在类中增加一个隐藏指针_vptr,这个指针在类实例化的时候生成,并且指向虚函数表的首地址。这个指针会被派生类所继承。
编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置。这意味着我们可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数了。
我们先来看看这样的一个程序:
这个函数定义了一狗狗类,讲完这个函数后,我们会继承这个狗狗类,创造出更多奇奇怪怪的狗。
(1)首先,在主函数中实例化了Dog类,得到一个aDog对象,&aDog,得到的是aDog对象的首地址。
(2)前面讲到,虚函数表的指针存在于对象实例中最前面的位置,而这个指针其实就是一个int类型的数。所以我们先把aDog对象的地址转换成指向int类型的指针(int*)&aDog。这个地方,单从值上来看&aDog与(int*)&aDog都是一样的,但前者只是一共单纯的地址值,那个内存地址上存放的是什么东东编译器并不知道;而(int*)&aDog则告诉编译器,那个地方存的是一个int的数(实际上,这个数就是虚函数表的首地址,而地址其实就是一个int的数嘛)。
(3)然后就可以放心地使用解引用符号*了,又因为解引用后的东东仍然是一个指针,所以前面还得加上(int*),于是便成了(int*)*(int*)&aDog,这个就是虚函数表的首地址。
(4)前面又说到,虚函数表其实就是一个函数指针的数组,那么虚函数表的首地址其实就是这个数组的首地址,那么数组里面存放的当然就是各个虚函数的函数指针了。接下来操作就完全是指针数组的操作啦。
(FUN)*((int*)*(int*)&aDog)
(FUN)*((int*)*(int*)&aDog+1)
(FUN)*((int*)*(int*)&aDog+2)
分别就是三个函数指针。
看到这里,额,晕了,是滴,我也晕。
我们对照运行结果和监视窗口来看看。
![](http://hi.csdn.net/attachment/201006/2/5148991_127549078673zR.jpg)