C++基础篇--虚函数原理

C++中多态的实现原理:虚函数表与虚函数指针 1-多态 多态的实现效果:多态实现了同样的调用语句具有不同的表现形态。 多态成立的三个条件:继承、徐函数重写、父类指针指向子类对象。 多态的C++实现方法:virtual关键字,告诉编译器要执行多态;不要根据指针类型来判断如何调用对应的函数,而是要根据指针所实际指向的对象的类型来判断如何调用。 多态的理论基础:动态联编(if、switch、多态)、静态联编、根据指针实际指向的对象的类型来判... 阅读详情

   虚函数算是C++最关键和核心的内容之一,是组件的基础。下面先列出一些相关名词,再围绕它们举例说明虚函数的本质实现原理。

基础概念(英文部分来自C++编程思想)

  1)绑定:Connectinga function call to a function body is called binding.(把函数调用和函数实现关联的过程)

  2)早绑定:Whenbinding is performed before the program is run (by the compiler and linker),it' s calledearly binding(程序运行前,即编译和链接阶段,完成的绑定即为早绑定)。

  3)迟绑定:latebinding, which means the binding occurs at runtime, based on the type of theobject.When a language implements late binding, there must be some mechanism todetermine the type of the object at runtime and call the appropriate memberfunction.(迟绑定发生在运行时,不同类型的对象绑定不同函数。实现迟绑定,必须有某种机制确定对象的具体类型然后调用合适的成员函数)。

  4)虚函数表(VTable):一个存储于常量区的函数指针表,类似函数指针数组。每个含有虚函数的类(基类及派生类)各自包含一张虚函数表(一个类一张表),表中依次存放虚函数地址。派生类vtable继承它各个基类的vtable,这里继承是指:基类vtable中包含某item,派生类vtable中也将包含同样item,但值可能不同。如派生类(override)重新实现了某虚函数,则它的vtable中该项item指向新写的虚函数,若未重新实现则沿用基类vtable中对应项的值。

  5) 指向虚函数表的指针(vtptr):所有包含虚函数的类所实例化的对象里,都包含该指针,运行时对象借助于它寻址到虚函数表,从而完成后绑定。因此每个包含虚函数的对象,相比普通对象会额外多占用一个指针型的存储空间。

 6) override,覆盖:派生类中使用同名同参的函数,重新定义基类某virtual函数的过程。其背后,编译器用指向派生类新函数的指针覆盖VTable中原来默认指向基类同名虚函数的指针。

魔术与揭秘

  class Base     //基类,包含virtual函数
  {
 public:
   virtual void output(){ cout << "Base::output()" << endl;}
  };
  //派生两个类Drv0和Drv1
  class Drv0 : public Base
  {
  public:
    void output () { cout << "Drv0:: output ()" << endl;}
  };
  class Drv1 : public Base
  {
  public:
    void output () { cout << "Drv1:: output ()" << endl;}
  };
  void main()
  {
    Base b;       
    Base* pb = &b;
    pb-> output ();             //输出Base::output()
 
    Drv0 d1;   
   pb =reinterpret_cast<Base*>(&d1);
   pb-> output ();               //输出Drv0:: output()
   Drv0 d2;
   pb =reinterpret_cast<Base*>(&d2);
   pb-> output ();              //输出Drv0:: output()
 
    Drv1 d3;
    pb = reinterpret_cast<Base*>(&d3);
    pb-> output ();            //输出Drv1:: output()
}

    经过一些中间封装变换,最终同样的”pb-> output ()”运行时选择了不同函数,得到不同结果。奇妙的魔术?别急,下面用C结构体实现类似功能(引入C中不存在的::,故下面为伪码):

    typedef void (*pvfun)();
    const pvfun pf_Base[2]= {Base::default,Base::output};
    const pvfun pf_Drv0[2]= {Base::default, Drv0::output};   // ④
    const pvfun pf_Drv1[2]= {Base::default, Drv1::output};   // ⑤
    typedef struct BASE
    {
      void *vtptr;
      int mBase;
    }Base;
    void Base::default()    {  printf("Base::default()");  }
    void Base::output()     {  printf("Base::output()");  }
 
    typedef struct DRV0
    {
      void *vtptr;
      int mBase;
      int mDrv0
    }Drv0;
    void Drv0::output()    {  printf("Drv0::output()");  }
 
    typedef struct DRV1
    {
      void *vtptr;
      int mBase;
      int mDrv1
    }Drv1;
    void Drv1::output()    {  printf("Drv1::output()");  }
 
    void main()
    {
      Base b;
      b.vtptr =pf_Base  ①
      Base* pb =&b; //
      *((pvfun)(pb->vtptr+0))();                //调用Base_default()
      *((pvfun)(pb->vtptr+sizeof(pvfun)))();    //调用Base_output()    ⑥
 
      Drv0 d1;   
      d1.vtptr =pf_Drv0   ②
      pb = (Base*)(&d1);
      *((pvfun)(pb->vtptr+0))();                //沿用Base_default()
      *((pvfun)(pb->vtptr+sizeof(pvfun)))();    //调用Drv0_output()    ⑦
      
      Drv0 d2;
      d2.vtptr =pf_Drv0 
      pb = (Base*)(&d2);
      *((pvfun)(pb->vtptr+0))();                //沿用Base_default()
      *((pvfun)(pb->vtptr+sizeof(pvfun))()      //调用Drv0_output ()   ⑧
 
      Drv1 d3;
      d3.vtptr =pf_Drv1   ③
      pb = (Base*)(&d3);
      *((pvfun)(pb->vtptr+0))()                //沿用Base_default ()
      *((pvfun)(pb->vtptr+sizeof(pvfun)))()    //调用Drv1_output ()
    }

    上例同样实现了用相同形式调用不同函数⑥⑦⑧,但这次能清楚看出猫腻所在:首先①②③处分别为结构体成员vtptr赋了不同值;其次pf_Drv0,pf_Drv1中第2个元素Base::output分别被新函数Drv0::output和Drv1::output覆盖 ④⑤。魔术揭穿了,还记得么:”所有软件问题都可以通过增加一个中间层解决”。表面的神奇是依靠VTable和vtptr组成的中间层在背后耍把戏。

    例中pf_Base/pf_Drv0/pf_Drv1就是虚函数表VTable;各结构体的成员vtptr就是指向VTable的指针;①②③处把vtptr与各自struct对应的VTable关联;Drv0和Drv1新定义同名函数(output)会覆盖pf_Drv0和pf_Drv1中对应元素,如未新定义则沿用Base中元素Base::default 见④⑤。只不过这些在C++中都隐藏不可见,由编译器自动生成和处理:

    1)虚函数表与类关联:编译器在编译时自动为每个包含虚函数的类及其派生类各自单独生成一张虚函数表,用于存放虚函数指针。注意:基类与派生类各有各的虚表,独立存放于不同地址,唯的一关联是:派生类如果没重新实现某基类虚函数,编译器在其VTable对应条目中默认存放基类虚函数地址作为后备;如果派生类重新实现某虚函数,则编译器在VTable中用新函数的地址代替默认的基类虚函数地址,这个过程即为上文的名词--override覆盖。

    2)对象与虚函数表关联:对包含虚函数的类,C++编译器为其每个对象插入一个指针成员vtptr,指向该类的虚函数表,即同类对象的vtptr值相同。vtptr值在构造函数中初始化(编译器自动加入),即使该类没定义构造函数,默认构造函数也会初始化vtptr。

    3)上面两步说明对象实例化一完毕,就已经和具体虚函数实现挂钩,调用时看似智能的选择不过是顺藤摸瓜:

      Drv0 d1;                           //这一步背后d1->vtptr= VTable(Drv0),其中VTable[0]=(*Drv0::output)()
      pb =reinterpret_cast<Base*>(&d1);  //编译器支持指针强制向上类型转换,把派生类对象的地址赋给基类指针,pb值仍是&d1
      pb-> output();                     //d1->vtptr[0](),即调用Drv0:: output ()

总结虚函数实现原理:

   编译期建立vtable表,设定表中元素;
  执行期间在对象创建时的构造函数中关联vtptr和vtable表;
  借助于指针支持的以小引大,通过强制转换将派生类对象的地址赋给基类指针;
  通过基类指针调用虚函数,先取得对象中的vtptr(obj->vtptr),再找到其所指的对应于特定父类或子类的虚函数表(VTable=*(vtptr)),然后表头加偏移量寻址到相应函数指针(vfunptr = VTable[offset]),最后执行*vfunptr()。

    这就是C++通过虚函数实现多态的背后原理,多态使我们可统一用指向基类对象的指针调用所有基类/派生类的虚函数实现,到底会调哪个,关键看对象的vtptr指针指向了哪个类的VTable,而这点在对象实例化时会通过构造函数隐含设置好。

 

  以一个问题结尾,可否在类的构造函数中调用虚函数,为什么?

深入浅出C++虚函数:从底层原理到实战应用 C++虚函数原理与应用解析:本文系统介绍了C++中虚函数的核心机制与实用技巧。深入剖析虚函数表(vtable)和虚指针(vptr)的工作原理,解释动态绑定的实现过程。重点说明不能声明为虚函数的场景(内联函数、构造函数等),强调虚析构函数的重要性。结合实际应用案例(插件系统、图形绘制等)展示虚函数的多态优势,并分析其性能开销。最后推荐系统学习资源,帮助开发者全面掌握C++多态特性,写出高效灵活的面向对象代码。 阅读详情

相关推荐

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(五)

《拉依达的嵌入式\驱动面试宝典》 整合了所有我认为可能遇到的技术面试问题。所有问题及其答案均是我个人学习后查阅资料总结,每个回答都做了仔细的分析,在嵌入式及其驱动面试相关问题解析做到全网独一无二。学习或者转行到嵌入式、驱动开发领域学习的不二之选

拉依达不拉跨的博客 392

C++虚函数原理

前言 C++的特性使得我们可以使用函数继承的方法快速实现开发,而为了满足多态(Polymorphism)这一性质,C++允许用户使用虚函数 (virtual function) 来完成 运行时决议 这一操作,这与一般的 编译时决定 有着本质的区别。多态的目的,就是要让处理“基类之对象”的程序代码,能完全无障碍的处理“派生类之对象” 虚函数表 对C++ 了解的人都应该知道虚函数(Virtual...

sinat_31608641的博客 2764

C++中虚函数的原理和作用

本文档详细介绍了C++中虚函数的原理以及在何场景下要用到虚函数。

详解C++虚函数原理

1、虚函数 虚函数是类中比较特殊的成员函数,通过在普通成员函数的前面加上“virtual”关键字声明。因为基类指针不能访问派生类的普通成员函数,但是可以访问派生类的虚函数,所以虚函数是实现C++多态性的关键。 派生类继承了基类的虚函数并重新定义(重写、覆盖)虚函数,通过基类指针去指向其派生类对象,进行动态绑定(延迟绑定),达到利用基类访问派生类虚函数的目的。如果没有虚函数,则总被限制在基类函数本身,无法调用派生类中被重写的函数。 2、虚函数实现原理 虚函数表 虚函数表指针 类对象在内存中的布

fscanf_fread的博客 2431

ESP-Mesh-Lite vs ESP-Mesh:如何选择适合你的物联网组网方案?

本文深入对比了乐鑫ESP-Mesh-Lite与ESP-Mesh两种物联网组网方案的核心差异。重点解析了ESP-Mesh-Lite分布式协议栈架构的优势,即每个节点拥有独立IP,可直接访问外部网络,极大简化了应用层开发。文章从网络架构、性能、开发体验及适用场景等多维度进行分析,为开发者根据项目需求(如分布式数据采集、智能家居或集中控制网络)选择最合适的方案提供了清晰的决策框架。

pluto的博客 817

ABAP内表数据导出Excel:OLE、ALV与GUI_DOWNLOAD方案全解析

在企业级应用开发中,数据导出是连接后台系统与前端业务分析的关键桥梁。其核心原理是将结构化数据转换为通用文件格式,以实现跨平台的数据交换与可视化。从技术价值看,一个健壮的导出功能能显著提升数据处理效率,减少人工干预,是衡量系统实用性的重要指标。常见的应用场景包括报表生成、数据交接和临时数据提取。本文聚焦于SAP ABAP开发领域,深入探讨如何将内表数据高效、稳定地导出至Excel,涵盖了OLE自动化、ALV标准导出以及基于CL_GUI_FRONTEND_SERVICES与GUI_DOWNLOAD的通用方案,并

weixin_32715349的博客 324

一文搞懂C++虚函数的实现原理

虚函数是实现C++中面向对象的三大特性之一——多态的一种技术手段。什么是多态呢? 在基类的成员函数前,使用修饰得到的就是虚函数。在派生类中,对继承得到的虚函数,进行重写,使得派生类对继承的方法有了自己的具体实现。在外部函数中,通过基类指针调用类的接口,在程序运行时,会根据指针绑定的对象选择对应的接口,这便是多态。多态简单来说,是让同一种事物有了多种不同的表现。基类指针绑定了类对象后,程序运行时,是怎么知道该运行哪个类的接口函数呢?这便是本篇文章要探究的问题。C++为了实现虚函数的动态绑定,在每一个类(当然是

garbage_man的博客 4354

运放+三极管构建LED恒流驱动电路:原理、设计与调试全解析

在电子工程领域,恒流源是一种能够提供稳定输出电流的电路,其核心原理基于负反馈控制机制。通过将输出电流采样转化为电压信号,并与一个稳定的参考电压进行比较,系统自动调节功率器件的导通状态,从而抵消输入电压波动或负载变化带来的影响,实现电流恒定。这种技术对于驱动LED、激光二极管等电流敏感型器件具有重要价值,能确保亮度、色温的一致性,并延长器件寿命。本文以经典的运放结合三极管方案为例,深入剖析其如何实现精准的电流“钳位”。该方案利用通用运算放大器作为比较器,配合三极管构成调整管,通过采样电阻实时监测电流,形成了一

weixin_42525800的博客 496

深度解码C++虚函数:原理、作用与最佳实践

虚函数是在父类中定义的一种特殊类型的函数,允许子类重写该函数以适应其自身需求。虚函数的调用取决于对象的实际类型,而不是指针或引用类型。通过将函数声明为虚函数,可以使继承层次结构中的每个子类都能够使用其自己的实现,从而提高代码的可扩展性和灵活性。在C++中,使用关键字"virtual"来定义虚函数。} };} };Animal类有一个虚函数makeSound(),它被Cat和Dog类继承并覆盖了该函数以实现不同的行为。

Lion_Long的博客 2068

C# 开发 ACS 运动控制系统:从入门到高阶应用实践

起始位置</param>/// <param name="endPos">结束位置</param>/// <param name="maxVelocity">最大速度</param>/// <param name="maxAcceleration">最大加速度</param>/// <param name="maxJerk">最大加加速度

品质生活,极致编程。 2842

从零开始:C#上位机集成YOLO完成摄像头目标追踪(保姆级教程)

新手入门C#上位机+YOLO做摄像头目标追踪,最忌讳的是“只抄代码,不懂逻辑”。这个教程不仅给了完整代码,还把核心原理和踩坑点都讲清楚了,新手跟着走,不仅能跑通demo,还能理解为什么这么写。如果你们在运行时遇到具体问题,比如模型导出失败、代码报错,都可以在评论区留言,我会一一解答。后续也可以出一期工业相机+YOLO目标追踪的教程,帮助大家把demo落地到实际生产中。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1030

C++虚函数原理理解

虚函数使得基类和派生类之间能够实现多态性,通过基类指针或引用动态调用派生类中重写的函数。底层实现基于虚函数表和虚函数表指针,在运行时动态决定调用哪个函数。使用虚函数时,通常在基类声明虚函数,派生类可以选择重写。虚析构函数是基类中有虚函数时常用的最佳实践,防止内存泄漏。

weixin_61256992的博客 1033

《大数据: Hive 交互命令和常用配置》

一、Hive 交互命令 1. 首先解决一个可能发生的问题 (log4 jar包冲突) 我这里解决方法就是删除一个,我删的是hive下的包 yexiang@hadoop1:<hive>$ hive SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/opt/soft/apache-hive-2.3.7-bin/lib/log4j-slf4j-impl-2.6.2.ja.

yexiang优快云的专栏 289

【C++篇】多态:从基础到虚函数与重写原理揭秘

摘要 本文深入探讨了C++中的多态特性,从概念定义到底层实现原理进行系统讲解。首先介绍了多态的定义和构成条件,强调继承是前提,需满足虚函数重写和基类指针/引用调用两个关键条件。详细解析了虚函数、虚函数重写(覆盖)的概念,并说明协变和析构函数重写两种特殊情况。通过内存模型分析虚表机制,揭示多态的实现原理:不同对象调用相同函数时通过虚表查找对应的虚函数实现。最后通过实验验证虚表存储位置,并对比静态绑定与动态绑定的区别。文章包含丰富代码示例和内存图示,帮助读者全面理解多态这一重要面向对象特性。

Surplus886的博客 951

使用Transformer实现自动调制识别(RML2016a)

image_size = 128 # resize重置输入大小patch_size = 2 # 从输入图片(信号看作图片)中将被提取的块大小# 分成 num_patches 个块num_patches = (image_size // patch_size) #(72/6)**2 横着裁剪),],return x)(x1, x1)# MLP.# Add MLP.x=x_train,y=y_train,filepath,round。

QAQIknow的博客 6276

【考试大纲】中级多媒体应用设计师考试大纲

中级多媒体应用设计师考试大纲

邓邓子的博客 382

C++虚函数详解

C++虚函数详解 前言 C++的特性使得我们可以使用函数继承的方法快速实现开发,而为了满足多态与泛型编程这一性质,C++允许用户使用虚函数**(virtual function)来完成运行时决议这一操作,这与一般的编译时决定**有着本质的区别。 虚函数表实现原理 虚函数的实现是由两个部分组成的,虚函数指针与虚函数表。 虚函数指针 虚函数指针**(virtual function pointer)*...

Whitesad的博客 12万+

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【C++】深入理解C++虚函数与纯虚函数

虚函数是在基类中使用关键字virtual声明的成员函数,它允许派生类对其进行重写(Override),实现运行时多态。当通过基类指针或引用调用虚函数时,实际调用的是对象类型对应的派生类中的函数,这个过程称为动态绑定(Dynamic Binding)或晚绑定(Late Binding)。虚函数重写指的是派生类中提供一个函数版本,该版本与基类中具有相同名称、相同返回类型和相同参数列表的虚函数相匹配。通过这种方式,派生类可以提供自己特定的实现,替换或扩展基类的行为。

Colorful___的博客 2万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

C++虚函数的作用和实现原理

一、什么是虚函数? 在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。 二、虚函数定义 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。下面来看一

weixin_44493195的博客 1万+

C++虚函数的底层实现原理

在C++中,多态是利用虚函数来实现的。比如说,有如下代码: #include using namespace std; class Animal { public: void Cry() { cout << "Animal cry!" << endl; } }; class Dog :public Animal { public: void Cry() { cout << "

面目全非,胸有丘壑 | Json Lee 2万+

C++虚函数全面解析:从原理到高级应用

虚函数是C++实现运行时多态的核心机制,通过virtualpublic:virtual void show() { // 虚函数声明virtual ~Base() {} // 虚析构函数编写更高效的代码避免常见陷阱设计更灵活的架构更好地进行调试和优化多态基类声明虚析构函数使用override明确覆盖关系避免在构造/析构中调用虚函数对性能关键代码考虑替代方案。

ikhsvjd的博客 2335

C++ 虚函数原理深度解析

C++ 虚函数原理深度解析

嗨,欢迎来到我的 优快云 博客小天地!一名深耕多年的技术发烧友。在这里,我将把日常工作中积累的宝贵经验,从复杂架构设计的精妙之处,到代码优化的实战技巧,毫无保留地分享给大家。 1339

C++多态,虚函数作用及底层实现原理

简述C++虚函数作用及底层实现原理 1.forewordC++是面向对象程序设计,其包括3项特点: (1)数据抽象:接口和实现分离 (2)继承:父类和子类 (3)多态:动态绑定本文讨论多态。当父类希望子类重新定义某些函数时,用virtual关键字声明为虚函数。当我们使用一个基类类型的引用或者指针,调用一个虚函数时就引发动态绑定/多态的发生。函数运行版本由传入的实参类型决定。可以用父类型的指针指

鸟恋旧林的博客 3905

[cpp进阶]多态和虚函数

文章目录多态的概念 多态的概念 多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。 C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。 比如说: 买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。 买票这一个行为,不同的人做就有不同的行为和结果,这就是多态。 ...

weixin_53027918的博客 1244

深入浅出理解C++虚函数:从原理到实践

虚函数是C++多态的核心,我们用几句话总结重点:虚函数通过virtual关键字声明,作用是实现动态绑定,让基类指针正确调用派生类的重写函数;虚函数的实现依赖于虚函数表(VTable)和虚指针(VPtr),VTable存储虚函数地址,VPtr指向所属类的VTable;基类析构函数必须是虚函数,避免删除派生类对象时内存泄漏;纯虚函数定义接口,包含纯虚函数的类是抽象类,不能实例化,派生类必须重写纯虚函数;重写虚函数要满足“三同”原则,C++11可用override关键字显式声明,减少错误。

2302_79266631的博客 1389

C++虚函数的底层实现原理详解_虚函数底层实现原理

简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。例:其中:B的虚函数表中存放着B::foo和B::bar两个函数指针。D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写(override)了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz。提示:为了描述方便,本文在探讨对象内存布局时,将忽略内存对齐对布局的影响。

2401_87555551的博客 1387
上一篇: C++基础篇--作用域和自定义命名空间
下一篇: C++基础篇--成员函数同名隐藏(overwrite)
ipmux
博客等级 码龄24年 161粉丝 65原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值