notes of c++ primer (4) -- 纠结的虚函数表 之一

  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)

  分别就是三个函数指针。

  看到这里,额,晕了,是滴,我也晕。

  我们对照运行结果和监视窗口来看看。

  通过上面两图可以看到,&aDog是类实例的首地址,_vfptr正是虚函数表的首地址,虚函数表中存放了三个虚函数的函数指针。
  最后画个图。
  以上是在没有继承的情况下,虚函数表的情况,如果有继承呢。。。等我看看书先,看明白了在讲,哈哈。。。以上一些地方说的有点乱,有误的地方,还请各位指教。 O(∩_∩)O~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值