• 【C++】多态中虚函数的底层理解


      👉引言💎

    在这里插入图片描述

    铭记于心
    🎉✨🎉我唯一知道的,便是我一无所知🎉✨🎉

    1 多态的基本概念

    1.1 什么是多态?

    • 多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为,通常是父类调用子类的重写函数,在C++中就是 父类指针指向子类对象,此时父类指针的向下引用就可以实现多态
    • 比如看下面的代码:
    class Animal
    {
    public:
      //虚函数
      virtual void speak()
      {
        cout << "动物在说话" << endl;
      }
    
      virtual void eat(int a )
      {
        cout << "动物在吃饭" << endl;
      }
    };
    
    class Cat :public Animal
    {
    public:
      void speak()
      {
        cout << "小猫在说话" << endl;
      }
    
      void eat(int a)
      {
        cout << "小猫在吃饭" << endl;
      }
    };
    class Dog :public Animal
    {
    public:
      void speak()
      {
        cout << "小狗在说话" << endl;
      }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这两个类实现集成关系,简单来说就是 Animal是父类,Cat是子类,通过父类引用调用子类函数,这就是多态(字面意思就是一个对象多个状态),这样就 符合** 高内聚低耦合** 的设计原则,更容易 后期维护与修改

    1.2 怎么实现多态

    说了这么多,那么多态到底怎么实现呢?

    • 想必上面代码中也已经透露出了一些信息,并且十分关键。没错,就是virtual,字面意思是 虚拟的,而也的确如此,这个关键字修饰的 函数,叫做虚函数,拥有虚函数的 类,被称为 抽象类(虚类),virtual是实现多态的必要不充分条件
    • 同时 ,子类要重写父类的虚函数,什么叫重写?就是函数类型,函数名,参数列表完全相同。
    • 并且父类指针还要指向子类对象,正如下所示:
    void doSpeak(Animal & animal) //Animal & animal = cat;
    {
      animal.speak();
    }
    
    void test01()
    {
      Cat cat;
      doSpeak(cat);
    
      Dog dog;
      doSpeak(dog);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如此方能满足实现多态的三个条件

    • 注:如果没有多态,那么父类指向子类时就无法调用子类函数,也就是此时该函数地址早就绑定好了,只能是animal说话,属于静态联编;如果想调用小猫说话,这个时候函数的地址就不能早就绑定好,而是在运行阶段再去绑定函数地址,属于地址晚绑定,叫动态联编,多态的实现就是依靠动态联编。

    2 虚函数的底层原理

    • 那么虚函数到底是怎么工作的呢?
    void test02()
    {
      Animal * animal = new Cat;
      ((void(*)()) (*(int *)*(int *)animal)) ();
      typedef void( __stdcall *FUNPOINT)(int);
      (FUNPOINT (*((int*)*(int*)animal + 1)))(10);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 就是animal->speak();
    ((void(*)()) (*(int *)*(int *)animal)) ();
    
    • 1
    • 这里 *(int *)animal 解引用到虚函数表中,先将animal类型的指针强转为int *,然后解引用得到int类型的值放到虚函数表中,然后偏移相应位置指向speak()
    ((void(*)()) (*(int *)*(int *)animal)) ();
    
    • 1

    在这里插入图片描述

    在这里插入图片描述

    • 调用猫说话,因为C/C++默认调用惯例 __cdecl,而用下列调用时 是__stdcall
    typedef void( __stdcall *FUNPOINT)(int);
    
    • 1
    Animal * animal = new Cat;
    
    • 1

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

    🌹写在最后💖
    路漫漫其修远兮,吾将上下而求索!伙伴们,再见!🌹🌹🌹在这里插入图片描述


  • 相关阅读:
    WS-FLV直播协议分析
    运维知识点-Windows操作系统cmd/Dos批处理命令与脚本手册bat
    前端学习-BFC,作用和原理,typeof,parseIntString()函数,Boolean()函数,深浅克隆
    基于Or-Tools的线性规划问题求解
    利用PaddleDetection 训练自定义COCO数据集进行目标检测
    第三十五章 在 Windows 上使用 IRIS(二)
    【第一章:Java开发岗:基础篇】
    Three.js+GeoJSON实现三维地图显示
    如何通过代理IP安全使用Linkedln领英?
    Java项目:SSM服装进销存管理系统
  • 原文地址:https://blog.csdn.net/runofsun/article/details/126044847