通过对象指针的方式强行指定到子类_C++中的虚指针与虚函数表

1a80b31c3c3390412a3ca1c34e680928.png

​ 最近在逛B站的时候发现有候捷老师的课程,如获至宝。因此,跟随他的讲解又复习了一遍关于C++的内容,收获也非常的大,对于某些模糊的概念及遗忘的内容又有了更深的认识。

以下内容是关于虚函数表、虚函数指针,而C++中的动态绑定实现和这两个内容是分不开的。

虚函数表、虚指针

​ 当一个类在实现的时候,如果存在一个或以上的虚函数时,那么这个类便会包含一张虚函数表。而当一个子类继承并重载了基类的虚函数时,它也会有自己的一张虚函数表。当我们在设计类的时候,如果把某个函数设置成虚函数时,也就表明我们希望子类在继承的时候能够有自己的实现方式;如果我们明确这个类不会被继承,那么就不应该有虚函数的出现。

下面是某个基类A的实现:

class 

​ 从下图中可以看到该类在内存中的存放形式,对于虚函数的调用是通过查虚函数表来进行的,每个虚函数在虚函数表中都存放着自己的一个地址,而如何在虚函数表中进行查找,则是通过虚指针来调用,在内存结构中它一般都会放在类最开始的地方,而对于普通函数则不需要通过查表操作。这张虚函数表是什么时候被创建的呢?它是在编译的时候产生,否则这个类的结构信息中也不会插入虚指针的地址信息。

eef44b74941216743cf9456f5b50afe5.png

以下例子包含了继承关系:

class 

以上三个类在内存中的排布关系如下图所示:

780deecb9ee2a08dee6764e78a8609df.png
  1. 对于非虚函数,三个类中虽然都有一个叫 func2 的函数,但他们彼此互不关联,因此都是各自独立的,不存在重载一说,在调用的时候也不需要进行查表的操作,直接调用即可。
  2. 由于子类B和子类C都是继承于基类A,因此他们都会存在一个虚指针用于指向虚函数表。注意,假如子类B和子类C中不存在虚函数,那么这时他们将共用基类A的一张虚函数表,在B和C中用虚指针指向该虚函数表即可。但是,上面的代码设计时子类B和子类C中都有一个虚函数 vfunc1,因此他们就需要各自产生一张虚函数表,并用各自的虚指针指向该表。由于子类B和子类C都对 vfunc1 作了重载,因此他们有三种不同的实现方式,函数地址也不尽相同,在使用的时候需要从各自类的虚函数表中去查找对应的 vfunc1 地址。
  3. 对于虚函数 vfunc2,两个子类都没有进行重载操作,所以基类A、子类B和子类C将共用一个 vfunc2,该虚函数的地址会分别保存在三个类的虚函数表中,但他们的地址是相同的。
  4. 从上图可以发现,在类对象的头部存放着一个虚指针,该虚指针指向了各自类所维护的虚函数表,再通过查找虚函数表中的地址来找到对应的虚函数。
  5. 对于类中的数据而言,子类中都会包含父类的信息。如上例中的子类C,它自己拥有一个变量 m_data1,似乎是和基类中的 m_data1 重名了,但其实他们并不存在联系,从存放的位置便可知晓。

关于动态绑定

在设计了以上三个类之后,我们就要开始对它们进行使用。

int 

​ 假如在程序中分别创建两个对象 a 和 b,a的创建是通过将b强制转化为类A得来的。对于 b.vfunc1() 的调用,应该没有太的疑问,它所调用的就是类B中的 vfunc1。而对于 a.vfunc1() 的调用,它虽然是强制转化后的结果,但并不能改变它是一个类A对象的事实,因此这里调用的便是类A中的 vfunc1,也就是上图中显示绿色的函数。

int 

​ 将程序改写成以上内容,pa 是一个类A的指针,但它指向的是一个类B的对象。在使用pa调用 vfunc1 的时候,程序发现pa是一个指针,并且现在正在调用一个虚函数叫做 vfunc1,这时通过 pa->vptr 这个虚指针到类B的虚函数中(上图的B vtbl)找对应的虚函数地址,找到该地址以后,就用相应的虚函数来进行调用,也就是调用上图所示的 B::vfunc1()

pa是类A的指针,为什么查找的是类B的虚函数表?
只要某一个类X包含虚函数,无论是它的父类或者它本身拥有,那么这个类的对象都会包含一个虚指针vptr,至于vptr要指向哪张表,取决于类X它本身是否含有虚函数。此处,类B中存在虚函数,那么它就会拥有自己的一张虚函数表。pa指向的是一个类B的对象,因此 p-vptr 指代的是类B中虚指针,所以它查找的是类B的虚函数表
如何从虚函数表中查找到 vfunc1 的地址?
虚函数表中的内容是在编译的时候确定的,通过以下方式进行查找 (* p->vptr[n] )(p) 或者 (* (p->vptr)[n] )(p),它的解读是:通过类对象指针p找到虚指针vptr,再查找到虚函数表中的第n个内容,并将他作为函数指针进行调用,调用时的入参是p(式子中的第二个p),而这个p就是隐藏的this指针,这里的n也是在编译的时候确定的。
int 

​ 再将程序修改成以上内容,对于 p2->vfunc1() 的调用和上文所述一致,它调用的是 B::vfunc1 函数。而对于 p1->vfunc1() 的调用,同样通过上面的方法可知, p1->vptr 它所指向的是类A的虚函数表,因此它调用的是 A::vfunc1 函数。

​ 通过以上内容,我们可以知道在使用基类指针调用虚函数的时候,它能够根据所指的类对象的不同来正确调用虚函数。而这些能够正常工作,得益于虚指针和虚函数表的引入,使得在程序运行期间能够动态调用函数。

动态绑定有以下三项条件要符合:

  1. 使用指针进行调用
  2. 指针属于up-cast后的
  3. 调用的是虚函数

与动态绑定相对应的是静态绑定,它属于编译的时候就确定下来的,如上文的非虚函数,他们是类对象直接可调用的,而不需要任何查表操作,因此调用的速度也快于虚函数。

参考

  1. https://www.bilibili.com/video/av48240648?p=1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/527418.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何手动输入给数组赋值_你是否真的了解VBA数组呢?让我带你认识一下真正的数组...

大家好,我们今日继续讲解VBA代码解决方案的第110讲内容:VBA数组讲解,什么是数组,如何定义数组,如何创建数组一、什么是数组 就是数组共享一个名字,有着多个元素按顺序排列的变量。在数组中,元素…

路由器装上去没有网络_5G网络那么快,今后路由器没有存在的价值了?

从一些宣传来看,即将商用的5G网速确实很快,号称10Gbps,比有线宽带还要快。既然5G网速那么快了,还有必要装宽带和路由器吗?先来纠正一个误区,5G网络的理论速度与实际速度是有很大差别的!!目前很多宣传中所说的5G网速最…

python绘制散点图的步骤_python如何绘制散点图?

饼图:饼图显示静态数字以及类别如何表示整体构成的一部分。饼图以百分比表示数字,所有段的总和需要等于100%。plt.pie(df[Age], labels {"A", "B", "C","D", "E", "F","G…

python问卷调查系统设计案例_从设计到施工,全面剖析超级玻璃屋面系统案例!...

【设计师的材料清单】戳图即可购买《设计师的材料清单》实体书或获取高清电子书转载整理自:弗思特(ID:FFTCCL)各位设计师大家好!随着人们对建筑外观及内部环境要求的不断提高具有大面积采光且轻盈的玻璃屋面系统在大型公共建筑设计中被广泛使…

二叉树的建立与遍历完整代码_腾讯面试官这样问我二叉树,我刚好都会

前记上周我投递出了简历,岗位是后端开发工程师。这周腾讯面试官给我进行了视频面试。面试过程中他问了二叉树的问题。二叉树相关算法题,在面试中出现的次数非常非常多,所以我面试之前也有所准备。今天结合面试问题详细讲一讲二叉树&#xff0…

创建mysql数据库图解_mysql数据库怎么创建外键?(图文+视频)

本篇文章主要给大家介绍mysql数据库怎么创建外键。关于mysql数据库外键的基础介绍,我们在这篇文章【Mysql外键是什么?有哪些用处?】中,已经给大家介绍过了,需要的朋友可以选择参考。了解了外键的基础定义,那…

redis 管理工具_Redis桌面管理工具Redis Desktop Manager 2019.2发布

简介Redis桌面管理器(又名RDM) - 是适用于Windows,Linux和MacOS的快速开源Redis数据库管理应用程序。该工具为您提供了一个易于使用的GUI,可以访问您的Redis数据库并执行一些基本操作:将键视为树,CRUD键,通过shell执行…

sql in里面可以放多少参数_如何从文本文件读入 SQL 参数

有时我们希望把参数列表分行存储在文本文件里,执行SQL时再拼到in函数里,从而查询出符合条件的记录。但SQL不能解析文本文件,直接拼到in函数里有困难,所以很多人先把文件导入数据库临时表,再用join语句做关联&#xff0…

谷歌浏览器32位安装包_谷歌浏览器发布紧急安全更新修复Blink内核中的任意代码执行漏洞...

上月底谷歌浏览器推送紧急安全更新对浏览器漏洞进行修复,当时谷歌浏览器博客并未公布漏洞的具体细节信息。蓝点网当时也在文章中称通常这种不公布漏洞的更新,都是比较严重的问题因此只有等多数用户修复后才会公开。现在多数能够自动更新的用户已经升级到…

java的serversocket_Java ServerSocket 实现聊天室功能(简易版)

大体实现功能可以选择房间,可以与房间中的用户进行实时的交流说在前面的话:serverSocket.accept() 会形成阻塞scanner.hasNextLine() 和 scanner.nextLine() 也会形成阻塞windows 下需要打开telnet服务。请自行百度实现思路:用户连接上服务器后选择房间号&#xff0…

ensp删除所有命令_HCIA学习笔记——eNSP配置NAT技术

今天用华为的eNSP做了关于NAT的实验,我把它发了出来,有需要的小伙伴可以收藏一下。分别配置了静态的NAT,NAT Outbound配置,Easy IP配置,NAT server配置。接口配置如图所示。在网关路由器上配置访问外网的默认路由。配置…

java程序员用代码写的情书_用代码写的三行情诗,你懂得程序员的浪漫吗?

原标题:用代码写的三行情诗,你懂得程序员的浪漫吗?在一般人眼里,程序员是一群古板,技术宅,不懂得浪漫的屌丝,但其实这些大神们浪漫起来也是相当可以的!下面让我们来看一下程序员们用…

对比四大企业级linux版本_Linux家族族谱

大体上来讲,Linux分为两个生态体系,红帽和debian。商业版本以Redhat为代表,开源社区版本则以debian为代表。红帽家族redhat :红帽自家服务器centos :基于红帽重新封装的去掉版权信息的免费版本fedora :基于…

mysql vim 命令_Vim基本命令必知必会

光标移动定位方式hjkl (强例推荐使用其移动光标,但不必需) →你也可以使用光标键 (←↓↑→). 注: j 就像下箭头0 → 数字零,到行首^ → 到本行第一个不是blank字符的位置(所谓blank字符就是空格,tab,换行,回车等)$ →…

内存条上面参数详解_为什么买内存条要看时序?别以为内存频率高性能就好

2016年开网吧买了400多根DDR4 8G内存条,一根180。2017年网吧赔了钱,我把电脑卖了,内存600一根,结果开网吧的钱赚回来了。囤内存挣回了开网吧的钱转眼间到2019年,内存就跌回了原来的价钱,200多块就又能买到D…

android 获取monkey日志_Android压力测试:monkey压力测试实战

主要分享的主题是 Android App 专项测试,通过 monkey 进行压力测试。一.测试步骤1.安装ADB2.连接被测手机和电脑3.打开CMD命令行4.输入monkey命令adb shell monkey -p your.package.name?--pct-touch 30?--ignore-crashes ?--ignore-timeouts--throttle 250?-s …

java工程怎么构造成moven_将普通java工程结构改为由maven管理的工程结构域

第一次尝试些博客记录一下自己的学习心得!1. http://maven.apache.org/download.cgi 这是maven 的下载链接进入链接下载一个maven 包这是我的windows10 系统下载的版本2 . 将下载好的文件解压后放入任意位置(一般建议放到自己常用的文件夹下边)并记住该路径G:\xiangmuwar …

python 函数进度条怎么_刷新你对进度条的认识,用python写出不一样的进度条

1 简介在日常工作中,我们运行程序经常会用到「循环迭代」,假如这个执行时间很短,那倒也无所谓。但是有一些过程耗时蛮长的,给其加上「进度条」(progress bar),可以帮我们监控代码执行进度,以及过程出现异常…

acer软件保护卡清除工具clear_使用Windows 10内置工具释放硬盘空间的最佳方法

如果您的磁盘空间不足,可以使用 Windows 10 中内置的工具来清理垃圾文件、应用程序和游戏等,以优化硬盘空间。使用这些清理工具不仅能够释放空间,而且还有助于提高系统性能。下面小编就为大家汇总介绍 几种种最佳快捷方法,来释放 …

python中astr是啥_python的基本操作

该来的还是来了,早先某个大胖子建议我写关于python基本操作的博客,当时没上心,现在来补点账,也姑且算是对往昔一点回忆吧。对了,那胖子,姓王,所幸不住我隔壁。闲言少叙,python和pych…