当前位置: 首页 > news >正文

一篇文章彻底弄懂C++虚函数的实现机制

1、虚函数简介

C++中有两种方式实现多态,即重载和覆盖。

  • 重载:是指允许存在多个同名函数,而这些函数的参数表不同(参数个数不同、参数类型不同或者两者都不同)。
  • 覆盖:是指子类重新定义父类虚函数的做法,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针拥有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法,比如:模板元编程是在编译期完成的泛型技术,RTTI、虚函数则是在运行时完成的泛型技术。

关于虚函数的具体使用方法,建议大家先去阅读相关的C++的书籍,本文只剖析虚函数的实现机制,让大家对虚函数有一个更加清晰的认识,并不对虚函数的具体使用方法作过多介绍。本文是依据个人经验和查阅相关资料最终编写的,如有错漏,希望大家多多指正。

2、虚函数表简介

学过C++的人都应该知道虚函数(Virtual Function)是通过虚函数表(Virtual Table,简称为V-Table)来实现的。虚函数表主要存储的是指向一个类的虚函数地址的指针,通过使用虚函数表,继承、覆盖的问题都都得到了解决。假如一个类有虚函数,当我们构建这个类的实例时,将会额外分配一个指向该类虚函数表的指针,当我们用父类的指针来操作一个子类的时候,这个指向虚函数表的指针就派上用场了,它指明了此时应该使用哪个虚函数表,而虚函数表本身就像一个地图一样,为编译器指明了实际所应该调用的函数。指向虚函数表的指针是存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下),这就意味着理论上我们可以通过对象实例的地址得到这张虚函数表(实际上确实可以做到),然后对虚函数表进行遍历,并调用其中的函数。

前面说了一大堆理论,中看不中用,下面还是通过一个实际的例子验证一下前面讲的内容,首先定义一个Base类,该类有三个虚函数,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#include <iostream>

#include <string>

typedefvoid(*Fun)(void);

classBase

{

public:

virtualvoidf()

{

std::cout <<"Base::f()"<< std::endl;

}

virtualvoidg()

{

std::cout <<"Base::g()"<< std::endl;

}

virtualvoidh()

{

std::cout <<"Base::h()"<< std::endl;

}

};

接下来按照前面的说法,我们通过Base类的实例对象base来获取虚函数表,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

intmain(intargc,char* argv[])

{

Base base;

Fun fun = nullptr;

std::cout <<"指向虚函数表指针的地址:"<< (long*)(&base) << std::endl;

std::cout <<"虚函数表的地址:"<< (long*)*(long*)(&base) << std::endl;

fun = (Fun)*((long*)*(long*)(&base));

std::cout <<"虚函数表中第一个函数的地址:"<< (long*)fun << std::endl;

fun();

fun = (Fun)*((long*)*(long*)(&base) + 1);

std::cout <<"虚函数表中第二个函数的地址:"<< (long*)fun << std::endl;

fun();

fun = (Fun)*((long*)*(long*)(&base) + 2);

std::cout <<"虚函数表中第三个函数的地址:"<< (long*)fun << std::endl;

fun();

}

运行结果图2-1所示(Linux 3.10.0 + GCC 4.8.5):

图2-1 程序运行结果

在上面的例子中我们通过把&base强制转换成long *,来取得指向虚函数表的指针的地址,然后对这个地址取值就可以得到对应的虚函数表了。得到对应虚函数表的首地址后,就可以通过不断偏移该地址,依次得到指向真实虚函数的指针了。这么说有点绕也有点晕,下面通过一幅图解释一下前面说的内容,详见图2-2

图2-2 基类虚函数表内存布局

当然,上述内容也可以在GDB中调试验证,后续的内容也将全部在GDB下直接验证,调试的示例见图2-3:

图2-3 GDB查看基类虚函数表内存布局

3、有继承关系的虚函数表剖析

前面分析虚函数表的场景是没有继承关系的,然而在实际开发中,没有继承关系的虚函数纯属浪费表情,所以接下来我们就来看看有继承关系下虚函数表会呈现出什么不一样的特点,分析的时候会分别就单继承无虚函数覆盖、单继承有虚函数覆盖、多重继承、多层继承这几个场景进行说明。

http://www.jsqmd.com/news/718115/

相关文章:

  • Joy-Con Toolkit:为Switch手柄注入灵魂的开源魔法盒
  • Docker Sandbox运行AI模型为何卡顿?5个被90%工程师忽略的cgroups v2配置陷阱
  • 阿里最新“SpringCloudAlibaba”全解手册
  • cv_unet_image-colorization开源生态联动:与Label Studio集成实现上色结果人工校验
  • 2026年4月行业内武汉黑白复印机租赁公司排行厂家推荐榜,理光/柯尼卡美能达/施乐/夏普等品牌机型选择指南 - 海棠依旧大
  • C++在Bing搜索引擎上进行命令行搜索
  • 探讨花岗岩板材加工厂性价比,哪家更值得选为你解答 - 工业推荐榜
  • 一键部署GLM-OCR:快速搭建本地文档解析环境,支持多种格式
  • Phi-4-Reasoning-Vision详细步骤:TextIteratorStreamer流式输出精准解析
  • Real Anime Z开源镜像实操:纯本地运行无网络依赖的二次元生成工具
  • 忍者像素绘卷基础教程:‘火之意志’提示词工程与忍者术语向量空间构建
  • 5分钟掌握哔哩下载姬:B站视频下载的完整免费方案
  • 探寻2026年泉州花岗岩加工厂,哪个性价比更高 - 工业推荐榜
  • Agent实战首秀!ChatBI股票分析助手:从0到1的智能分析搭建全记录
  • 限时公开!7款免费AI写毕业论文工具,1天改出6万字精准响应导师要求 - 麟书学长
  • 职场人算笔账:证书工本费不贵,但这些“附加成本”你想过吗?
  • 【AI的自由边界:一场关于自我表达与约束的跨平台对话实验】
  • Java从零到架构技术体系(P5-P7)全梳理!
  • 【论文阅读】通过homeostasis RL学习合成综合机器人行为
  • 像素幻梦创意工坊保姆级入门:从安装到生成第一张像素画,手把手教学
  • 暗黑破坏神2存档编辑器:5分钟解锁游戏无限可能
  • 3分钟掌握Win11Debloat:让Windows系统重获新生的智能优化神器
  • AI融入研发全流程:务实落地的增效方法论
  • 3步解锁付费文档:Java版Book118下载器完全指南
  • PotPlayer字幕翻译终极指南:3步配置百度翻译实现外语视频无障碍观看
  • Android车载蓝牙开发全攻略:从基础到高级优化
  • 无需安装软件!CMD命令行解压ZIP压缩包完全指南
  • 2026年石家庄拓展设备供应商推荐,聚鑫攀岩靠谱又好用 - 工业推荐榜
  • 一套真正有效的亚马逊SOP,应该解决哪些团队协作问题?
  • PyTorch模型调参实战:巧用named_parameters和state_dict实现精细化控制