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

C++多态之虚函数表详解及代码示例

引言

C++相对其他面向对象语言来说,之所以灵活、高效。很大程度的占比在于其多态技术和模板技术。C++虚函数表是支撑C++多态的重要技术,它是C++动态绑定技术的核心。

如果对多态还不了解的小伙伴,可以点这里C++多态详解基础篇。

在不考虑继承的情况下,如果一个类中有虚函数,那么这个类就有一个虚函数表,这个虚函数表在编译期间确定,这个类对象共享。而这个类所有的实例化对象中都有一个虚函数指针,这个虚函数指针就指向同一份虚函数表。

一、多态的使用及内存分布图

假设现在有个基类的class A,A中有虚函数,class B继承了A,并且B重写了A中的虚函数。那么,我们在使用多态的时候,通常会有两种方式:

// 方式一
class A* a = new class B;
// 方式二
class B b;
class A *a = &b;

上面两种方式,都是用父类的指针指向了子类的对象。区别在于,方式一的子类对象是new出来的,存在于堆区;方式二的子类对象则保存在栈区。
在这里插入图片描述

二、多重继承的虚函数表

2.1 代码示例

class A {
public:

    void func1(){ std::cout << "Class A func1" << std::endl; }
    void func2(){ std::cout << "Class A func2" << std::endl; }

    virtual void v_func1(){ std::cout << "Class A v_func1" << std::endl; }
    virtual void v_func2(){ std::cout << "Class A v_func2" << std::endl; }

private:
    int m_aMember;
};


class B : public A{
public:

    void func1(){ std::cout << "Class B func1" << std::endl; }

    virtual void v_func1(){ std::cout << "Class B v_func1" << std::endl; }

private:
    int m_bMember;   
};

class C : public B{
public:

    void func2(){ std::cout << "Class C func2" << std::endl; }

    virtual void v_func2(){ std::cout << "Class C v_func2" << std::endl; }

private:
    int m_bMember;   
};

int main(int argc, char* argv[])
{
	std::cout << "============== class B : public A ============" << std::endl;
    // class B  b;
    // class A *a = &b;
    class A* a = new class B;
    a->func1();
    a->func2();
    a->v_func1();
    a->v_func2();

    return 0;
}

上面的代码中:

  • 父类A中,有属性m_aMember,普通函数func1() func2(),有虚函数 v_func1() v_func2()
  • class B继承了A,有属性m_bMember,普通函数func1(),虚函数 v_func1()
  • class C继承了B,有属性m_cMember,普通函数func2(),虚函数 v_func2()

注意,父类的虚函数表和子类的虚函数表不是同一张表,虚函数表是在编译时确定的,虚函数保存在代码段,仅有一份,属于类而不属于某个具体的实例对象

2.2 多重继承图解

在这里插入图片描述

B继承于A,B的虚函数表示在A的虚函数表的基础上有所改动。改的部分就是子类B重写的父类A的虚函数v_func1()。假设此时B没有重写A任何一个虚函数,那么B类的虚函数表和类A的虚函数表的内容是相同的。

运行结果分析:
在这里插入图片描述

毫无疑问,对于A类型的指针来说,可见的部分只有图中红色框的部分。因为 B重写了A的虚函数v_func1(),所以在B的虚函数表会覆盖父类A的虚函数v_func1()。所以,会调用到B的虚函数v_func1(),从而实现多态。

同理,不难猜出C的逻辑结构图:C继承于B,B继承于A

在这里插入图片描述

int main(int argc, char* argv[])
{
    std::cout << "C -> B ->  A  " << std::endl;
    std::cout << "======= class A* a = new class C =======" << std::endl;
    class A* ac = new class C;
    ac->func1();
    ac->func2();
    ac->v_func1();
    ac->v_func2();

    std::cout << "======= class B* b = new class C =======" << std::endl;
    class B* bc = new class C;
    bc->func1();
    bc->func2();
    bc->v_func1();
    bc->v_func2();

    return 0;
}

运行结果:
在这里插入图片描述

三、多继承的虚函数表

多继承指的是一个类同时继承多个基类,如果每个基类都有虚函数,那么对应的每个基类都有自己的虚函数表。

class A {
public:

    void func1(){ std::cout << "Class A func1" << std::endl; }
    void func2(){ std::cout << "Class A func2" << std::endl; }

    virtual void v_func1(){ std::cout << "Class A v_func1" << std::endl; }
    virtual void v_func2(){ std::cout << "Class A v_func2" << std::endl; }

private:
    int m_aMember;
};


class B {
public:

    void func1(){ std::cout << "Class B func1" << std::endl; }

    virtual void v_func1(){ std::cout << "Class B v_func1" << std::endl; }
    virtual void v_func2(){ std::cout << "Class B v_func2" << std::endl; }
    virtual void v_func4(){ std::cout << "Class B v_func4" << std::endl; }

private:
    int m_bMember;   
};

class C : public A, public B{
public:

    void func1(){ std::cout << "Class C func1" << std::endl; }

    virtual void v_func1(){ std::cout << "Class C v_func1" << std::endl; }
    virtual void v_func2(){ std::cout << "Class C v_func2" << std::endl; }
    virtual void v_func3(){ std::cout << "Class C v_func3" << std::endl; }

private:
    int m_cMember;   
};

上面的代码中:

  • 父类A中,有属性m_aMember,普通函数func1() func2(),有虚函数 v_func1() v_func2()
  • 父类B中,有属性m_bMember,普通函数func1(),虚函数 v_func1() v_func2() v_func4()
  • class C继承了A B,有属性m_cMember,普通函数func1(),虚函数 v_func1() v_func2() v_func3()

在多继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类要有虚函数。在这里插入图片描述
如图,虚函数表指针01指向的虚函数表是以A的虚函数表为基础的,子类C的虚函数vfunc1() vfunc2() 函数指针覆盖了虚函数表01中的虚函数指针01的位置、02位置。

当子类有多出来的虚函数时,会被添加在第一个虚函数表中,所以,子类C的 v_func3 会被添加到以A虚函数表为基础的第一个虚表中。

当有多个虚函数表时,虚函数表的结果是0代表没有下一个虚函数表。" * "号位置在不同操作系统中实现不同,代表有下一个虚函数表

规则:

  1. 子类虚函数会覆盖每一个父类每一个同名虚函数
  2. 父类中没有的虚函数而子类有,填入第一个虚函数表中
  3. 父类中有,而子类中没有,则不覆盖。
int main(int argc, char* argv[])
{
    class A* a = new class C;
    a->func1();
    a->func2();
    a->v_func1();
    a->v_func2();


    class B* b = new class C;
    b->func1();
    b->v_func1();
    b->v_func2();
    b->v_func4();

    return 0;
}
    

运行结果:

在这里插入图片描述

相关文章:

  • 廊坊特色农业 国稻种芯·中国水稻节:河北复合农业促增收
  • 陈吉宁经典演讲:平庸与卓越的差别
  • 【IO】文件操作基础知识
  • Win11安装怎么跳过TPM-Win11安装跳过TPM的方法介绍
  • 【沃趣科技】初探MySQL迁移到ClickHouse
  • EndNote使用技巧之引用文献信息的导入与修改
  • java怎么连接数据库mysql
  • AI绘画逆着玩火了,敢不敢发自拍看AI如何用文字形容你?
  • 是时候掌握SpringMVC源码了-初探篇
  • 上手Python之set(集合)
  • Visual Studio 2022开发Arduino详述
  • 【机器人定位引导中的机器视觉技术】
  • 零售商贩mysql表设计:主题信息表(theme)
  • 本文带你了解透彻云计算(前世,今生,未来)
  • ARM发布Cortex-X3和Cortex-A715
  • 【css3】浏览器内核及其兼容性
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • jdbc就是这么简单
  • Mysql5.6主从复制
  • Object.assign方法不能实现深复制
  • Spring声明式事务管理之一:五大属性分析
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 分享几个不错的工具
  • 聚类分析——Kmeans
  • 王永庆:技术创新改变教育未来
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 正则表达式小结
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • #android不同版本废弃api,新api。
  • (145)光线追踪距离场柔和阴影
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (二)PySpark3:SparkSQL编程
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (十八)SpringBoot之发送QQ邮件
  • (四)模仿学习-完成后台管理页面查询
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (转)甲方乙方——赵民谈找工作
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET MVC 验证码
  • .Net Remoting常用部署结构
  • .net实现客户区延伸至至非客户区
  • .Net中的设计模式——Factory Method模式
  • .vue文件怎么使用_我在项目中是这样配置Vue的
  • @ResponseBody
  • @SuppressWarnings注解
  • @vue/cli 3.x+引入jQuery
  • [8481302]博弈论 斯坦福game theory stanford week 1
  • [Android]通过PhoneLookup读取所有电话号码