| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++多态的底层原理 -> 正文阅读 |
|
[C++知识库]C++多态的底层原理 |
文章目录零.前言要了解C++多态的底层原理需要我们对C指针有着深入的了解,这个在打印虚表的时候就可以见功底,理解了多态的本质我们才能记忆的更牢,使用起来更加得心应手。 1.虚函数表(1)虚函数表指针首先我们在基类Base中定义一个虚函数,然后观察Base类型对象b的大小:
我们发现,如果按照对齐数原则来计算b的大小时,得到的结果是8,而我们打印的结果是: 我们发现对象中多了一个__vfptr,即为虚函数表指针。简称为虚表指针。 (2)虚函数表仍然看上图,我们发现虚函数表指针下方有两个地址,这两个地址分别对应的就是Base中两个虚函数的地址,构成了一个虚函数表。所以虚函数表本质是一个指针数组,数组中每一个元素是一个虚函数的地址。
其中bb调用f()的过程没有发生解引用操作,非虚函数在公共代码段中,直接对其进行调用即可。而bb调用Func1()的过程中,需要通过虚表指针来找到Func1(),而拿到虚表指针需要对bb进行解引用操作,而bb是空,因此程序会崩溃。 2.虚函数表的继承–重写(覆盖)的原理还拿上一节中买票的例子举例,其中父类中有两个虚函数,子类重写了其中的一个,子类中还有自己的函数。
我们可以通过调试来观察一下他们的虚表和虚表指针。显然父类对象__vfptr[0]中存放的是BuyTicket的地址,__vfptr[1]中存放的是Func1()的地址。子类对象中__vfptr[0]中存放的是继承并重写的BuyTicket的地址,__vfptr[1]中存放的是继承下来但没有进行重写的Func1()的地址。通过对比我们发现:对于没有进行重写的Func1()来说,子类中虚表中的地址和父类中的是一样的,可以说是直接拷贝下来的。而对于进行了重写的BuyTicket来说,子类中虚表的地址与父类中明显不一样,其实是在拷贝了父类的地址后又进行了覆盖的。因此重写从底层的角度来说又叫做覆盖。 3.观察虚表的方法(1)内存观察
(2)打印虚表虚表的地址通过观察内存,对于单继承来说,我们只需要打印对象的首元素的地址即可找到虚表,并进行打印。
下面来解释一下如何打印的虚表,分为两部分,一部分是函数,一部分是传参: 函数首先我们明确,虚函数指针是一个函数指针,因此为了简便我们可以将函数指针重命名为vfptr。 传参拿父类对象a举例,我们要找到a的前四个字节的内容,即为虚表指针,然后再传入函数中。 有了前面的解释,我们就可以理解打印虚表的原理了,我们把这段代码运行一下: (3)虚表的位置我们还可以观察一下虚表的位置,在哪个区域:
打印的结果是: 4.多态的底层过程
我们还使用这一段代码来举例,首先复习一下多态:使用父类的指针或者引用去接收子类或者父类的对象,使用该指针或者引用调用虚函数,调用的是父类或子类中不同的虚函数。 5.几个原理性问题了解了多态原理之后,就可以分析出在上一节中出现的一些现象规律。 (1)虚表中函数是公用的吗?虚表中的函数和类中的普通函数一样是放在代码段的,只是虚函数还需要将地址存一份到虚表,方便实现多态。这也就说明同一类型的不同对象的虚表指针是相同的,我们还可以通过调试观察:
(2)为什么必须传入指针或引用而不能使用对象?当我们使用父类对象去接收时,父类对象本身就具有一个虚表了,当子类对象传给父类对象的时候,其他内容会发生拷贝,但是虚表不会,C++这样处理的原因在于,如果虚表也会发生拷贝的话,那么该父类对象的虚表就存了子类对象的虚表,这是不合理的。
(3)为什么私有虚函数也能实现多态?这是因为编译器调用了父类的public接口,由于是父类的引用或者指针,因此编译器发现是public之后就不再进行检查了,只要在虚表中可以找到就能调用函数。 (4)VS中的虚表中存的是指令地址?在VS2019中,为了封装严密,其实虚表中存入的是跳转指令,我们可以通过反汇编进行观察: 6.多继承中的虚表谈到多继承就要谈到菱形虚拟继承,这是一个庞大而复杂的问题,需要更大的大佬来解释。
我们可以使用调试来观察a中的虚表内容:
注意需要先将&a转换成char*类型,这样对其加一,才代表加一个字节。 7.总结实际中我们不建议设定出菱形继承或者菱形虚拟继承,在实际中很少用,这里推荐大佬的两篇文章C++虚函数表解析,C++对象的内存布局,对我们的提升有很大的帮助,看一些原理书似乎用时长成效难以体现,但是真正静下心来深入理解的人最后都会有所成就,当你经过七重的孤独,才能成为真正的强者! |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 22:34:30- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |