欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > 【C++】多态(下)

【C++】多态(下)

2025/3/17 17:29:58 来源:https://blog.csdn.net/qq_75000174/article/details/145424896  浏览:    关键词:【C++】多态(下)

大家好,我是苏貝,本篇博客带大家了解C++的多态,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 4. 多态的原理
    • 4.1 虚函数表
    • 4.2 多态的原理
    • 4.3 动态绑定与静态绑定
  • 5. 单继承和多继承关系的虚函数表
    • 5.1 单继承中的虚函数表
    • 5.2 多继承中的虚函数表
    • 5.3 菱形继承/菱形虚拟继承(了解)

4. 多态的原理

4.1 虚函数表

问:下面代码的结果是什么?
在这里插入图片描述

答案是8,为什么?类Base里只有1个int类型的变量,它占4个字节,结果是8,那说明还有4个字节是因为虚函数存在的,是什么?看下图
在这里插入图片描述

通过观察测试我们发现b对象中除了_b成员,还多一个__vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。

一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。那么派生类中这个表放了些什么呢?我们接着往下分
在这里插入图片描述

虚函数表里存放的是虚函数的地址

我们再来多一些虚函数
在这里插入图片描述
在这里插入图片描述

通过观察和测试,我们发现了以下几点问题:
1、 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,父类的虚表指针也继承了,另一部分是自己的成员
2、 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
3、 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表
4、 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

总结一下派生类的虚表生成:

  1. 先将基类中的虚表内容拷贝一份到派生类虚表中
  2. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数地址覆盖虚表中基类的虚函数地址
  3. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

上面说的都是不同类的对象的虚函数表,那如果是同类的对象,它们的虚函数表是什么情况呢?
在这里插入图片描述

同类的对象共用一个虚函数表

4.2 多态的原理

上面分析这个半天了,那么多态的原理到底是什么?还记得下面的Func函数传Person调用的 Person::BuyTicket,传Student调用的是Student::BuyTicket
在这里插入图片描述
在这里插入图片描述

1、 观察上图的红色箭头我们看到,p是指向pt对象时,p->BuyTicket在pt的虚表中找到的虚函数是Person::BuyTicket。
2、 观察上图的蓝色箭头我们看到,p是指向st对象时,p->BuyTicket在st的虚表中找到的虚函数是Student::BuyTicket。
3、 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

满足多态以后的函数调用需要的函数地址,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时需要的函数地址是编译时确认好的。

现在我们来打印一下对象d的虚表
在这里插入图片描述

根据上面的学习,我们知道,虚表是一个函数指针数组,
在这里插入图片描述

但是按照上图的函数指针数组的格式就太麻烦了,因此我们typedef一下
在这里插入图片描述

再写一个打印虚表的函数
在这里插入图片描述

最后只需要将虚表指针传进Print函数,即需要将d的第一个4字节的内容(虚表指针)作为参数
我们可以将d强转成int类型的吗?不能,因为只有相关联的类型才能相互转换

在这里插入图片描述

所以我们先&d,再取出前4个字节的地址,即(int*)&d,再得到前4个字节地址的内容,即*((int*)&d),最后在将它强转成VFPTR*即可
在这里插入图片描述

在这里插入图片描述

我们还想知道这3个函数地址分别对应哪个函数怎么办?调用对应函数

在这里插入图片描述

在这里插入图片描述

这里还有一个很容易混淆的问题:虚函数存在于哪个区域? 虚表存在于哪个区域?
答:虚表存的是虚函数指针,不是虚函数,虚函数和普通成员函数一样的,都是存在于代码段(即常量区)的,只是它的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下也是存在于代码段的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通过结果我们发现,A和B的虚表地址离位于常量区的”hhhhh”更近,所以大致能证明虚表存在于常量区(即代码段)

4.3 动态绑定与静态绑定

1、 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载
2、 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

5. 单继承和多继承关系的虚函数表

需要注意的是在单继承和多继承关系中的虚函数表,下面我们去关注的是派生类对象的虚表模型,因为基类的虚表模型前面我们已经看过了,没什么需要特别研究的

5.1 单继承中的虚函数表

在这里插入图片描述

观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。那么我们如何查看d的虚表呢?

在这里插入图片描述

1、 打开内存窗口
在这里插入图片描述

2、 使用代码打印出虚表中的函数。(在11.4的第2点多态的原理的最后有详细介绍)

在这里插入图片描述
在这里插入图片描述

结论:
如果子类有虚函数,继承的父类有虚函数表指针,那就将子类的虚函数地址放到第一个有虚函数表指针的父类的虚函数表中。
如果继承的父类没有虚函数表指针,那就子类自己创建一个虚函数表并存储虚表的地址

5.2 多继承中的虚函数表

在这里插入图片描述

问:上面程序的答案是什么?
在这里插入图片描述

答案:20,为什么?

在这里插入图片描述

Derive本身没有虚表指针吗?没有,因为它继承的基类有虚函数表,所以Derive将func3的函数地址放到了第一个虚表指针所指向的虚表中

下面通过打印虚函数表来证明
在这里插入图片描述
在这里插入图片描述

如果出现上图的情况,那就先清理解决方案,再重新生成解决方案
在这里插入图片描述

再运行程序就正常了
在这里插入图片描述

观察上图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

5.3 菱形继承/菱形虚拟继承(了解)

1、菱形继承:
在这里插入图片描述

在这里插入图片描述

菱形继承的对象模型和多继承相似,如果派生类有不是继承的虚函数A,如果继承的基类有虚表指针,那么将虚函数A放到第一个有虚表指针的基类的虚表中。

在这里插入图片描述

2、菱形虚拟继承
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

虚基表:在菱形继承那部分有讲到


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词