• 【C++】多态 ⑦ ( 多态机制实现原理 | 虚函数表概念 | 虚函数表工作机制 | vptr 指针 | 虚函数表运行时机制 | 虚函数与动态联编 )






    一、多态原理




    1、多态成立的三个条件


    " 多态 " 实现需要满足以下三个条件 :

    • 首先 , 要有 继承关系 ;
    • 然后 , 父类中的函数需要有 virtual 关键字修饰 , 子类重写该 " 虚函数 " ;
    • 最后 , 父类指针 或 父类引用 指向 子类的对象 ;

    满足 ① 继承 , ② 虚函数重写 , ③ 父类指针/引用指向子类对象 三个条件 , 即可实现多态 ;


    2、虚函数表概念


    " 多态 " 的机制 , 由 " 虚函数表 " 实现 ;

    " 虚函数表 " , 英文名称为 " Virtual Function Table " , 简称 Vtable ;


    C++ 编译器 通过将 虚函数指针 放入 基类 的 虚函数表中 , 实现在 运行时 根据实际对象的类型 来调用对应的 virtual 虚函数 ;


    虚函数表 是由 C++ 编译器 自动维护的 , 对 程序员 透明 ;


    3、虚函数表工作机制


    " 虚函数表 " 由 C++ 编译器 负责 创建 与 维护 , 被 virtual 关键字 修饰的 虚函数 , 会自动 被 C++ 编译器 存储到 " 虚函数表 " 中 ;


    虚函数表 创建 : 在 类 中使用 virtual 关键字 声明 虚函数 时 , C++ 编译器 会自动为该类生成 " 虚函数表 " ;

    • 生成虚函数表的前提是 至少有 1 个虚函数 ;
    • 如果 没有虚函数 , 就不会生成虚函数表 ;
    • 如果 类 中有 virtual 虚函数 , 则 该类的 每个对象 中 , 都有一个 指向 虚函数表的 vptr 指针 ;

    虚函数表 存储 虚函数指针 : " 虚函数表 " 是 存储 " 类成员函数指针 " 的 数据结构 , 是一个 函数指针数组 , 数组中的元素都是函数指针 , 具体存储的都是 指向 类中的虚函数 的指针 ;

    • 如果 子类 中 , 重写了 父类的 virtual 虚函数 , 那么 C++ 编译器会在 子类 虚函数表 中放入该 子类虚函数的 函数指针 ;

    4、vptr 指针


    如果 C++ 类中存在 virtual 虚函数 , 在创建对象时 , 会生成 虚函数表 Virtual Function Table , 简称 vtable ;

    C++ 编译器 编译 代码时 , 会自动为该类 添加 一个 vptr 指针 成员变量 , 该指针 会指向 虚函数表 ;


    5、虚函数表运行时机制


    " 虚函数表 " 在 C++ 编译器 编译 时 生成 , 运行时 存储在可执行文件的内存中 ;

    程序运行时 , 根据对象的类型信息 , 可以通过 虚函数表 来动态地调用对应的函数 ;


    虚函数表 与 对象 是一一对应的 , 如果 父类指针 指向 的对象 , 调用 虚函数 , 则会去 对象对应的 虚函数表 中查找函数 , 找到的肯定是 对象的 虚函数 ;

    虚函数表机制 可以避免在运行时进行类型判断 , 提高了程序的效率和可维护性 ;


    6、虚函数与动态联编


    C++ 编译器 确定 函数 是否为 virtual 虚函数 ;

    • 非虚函数的静态联编 : 如果 函数 没有被 virtual 关键字修饰 , 该函数 不是 虚函数 , 该函数 可以被确定为 普通 成员函数 , 则使用 " 静态联编 " , 在编译时 就可以确定 是否调用该函数 ;
    • 虚函数的动态联编 : 如果 函数 被 virtual 关键字修饰 , 则该函数是 虚函数 , C++ 编译器编译该类时 , 会自动生成一个 虚函数表 , 并为对象设置一个 vptr 指针 , 指向该 虚函数表 , 在调用时 , 需要查找 vptr 指向的 虚函数表 中的 虚函数 , 查找个调用 虚函数 的操作是在运行时进行的 , 这是 " 动态联编 " ;




    二、代码示例 - 虚函数表




    1、代码实例分析 - 虚函数表创建与使用


    在下面的代码中 , Parent 是父类 , 其中定义了 virtual 虚函数 , Child 子类中重写了该 虚函数 ;

    使用 如下代码 , 创建 Child 子类对象时 , 发现有 virtual 虚函数 会创建 虚函数表 , 在对象中会自动添加 vptr 指针 成员变量 指向 虚函数表 首地址 ;

    	// 创建 Child 子类对象时
    	// 发现有 virtual 虚函数 会创建 虚函数表
    	// 在对象中使用 vptr 指针指向 虚函数表 首地址
    	Child c;
    
    • 1
    • 2
    • 3
    • 4

    虚函数表 中 存储了 多个 虚函数 的 入口地址 ;


    将 Child 对象的地址 赋值给 Parent * 指针时 , 通过 Parent* 指针调用 fun 虚函数 ,

    C++ 编译器 根本不会去关心 当前调用的函数 是 Parent 父类的 还是 Child 子类的 ,

    而是根据对象中的 vptr 指针 指向的 虚函数表 调用 对应的 虚函数 ;


    父类对象 和 子类对象 中 都有一个 vptr 指针 成员变量 ,

    当调用 虚函数 时 , 会根据对象中的 vptr 指针查找 虚函数表 中的 对应 虚函数 ;


    2、完整代码示例


    代码示例 :

    #include "iostream"
    using namespace std;
    
    // 父类
    class Parent {
    public:
    	virtual void fun(int a)
    	{
    		cout << "执行 父类 virtual void fun(int a) 函数" << endl;
    	}
    };
    
    // 子类
    class Child : public Parent {
    public:
    	virtual void fun(int a)
    	{
    		cout << "执行 子类 virtual void fun(int a) 函数" << endl;
    	}
    };
    
    int main() {
    
    	Parent* p;
    	// 创建 Child 子类对象时
    	// 发现有 virtual 虚函数 会创建 虚函数表
    	// 在对象中使用 vptr 指针指向 虚函数表 首地址
    	Child c;
    
    	// 将父类指针指向子类对象
    	p = &c;
    	// 通过父类指针调用子类对象的 fun 函数
    	p->fun(1);
    
    	// 控制台暂停 , 按任意键继续向后执行
    	system("pause");
    
    	return 0;
    }
    
    • 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
    • 38
    • 39

    执行结果 :

    执行 子类 virtual void fun(int a) 函数
    Press any key to continue . . .
    
    • 1
    • 2

    在这里插入图片描述

  • 相关阅读:
    docker安装
    STM32F103C8t SPI1重映射到PB3 PB4 PB5无输出
    小区搜索(二)CORESET0
    使用gitee go将spring boot项目部署到云主机上并运行
    多语言多商户多货币跨境电商商城源码(一键铺货\订单返现商城源码搭建开发)
    excel中怎样将数据合并到一个单元格用逗号隔开
    Kotlin inline、noinline、crossinline 深入解析
    Electron程序逆向(asar归档解包)
    JavaSE 利用正则表达式进行本地和网络爬取数据(爬虫)
    Navisworks二次开发——图元属性获取
  • 原文地址:https://blog.csdn.net/han1202012/article/details/134126238