C 中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。

虚函数表

每个含有虚函数的类都有一个虚函数表(Virtual Table)来实现的。简称为V-Table。C 的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

1、 每一个类都有虚函数列表。

2、 虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。

3、 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同,子类独有的虚函数放在后面。

当定义一个有虚函数类的对象时,对象的第一块的内存空间就是一个指向虚函数列表的指针。

在这举个例子

假设我们有这样的一个类:

由于例程的操作环境是64位系统,所以用long*强转。其中(long*)(&b)就是虚函数表地址,(long*)*(long*)(&b)就是第一个函数地址,代码运行结果如下:

在程序中取出对象b的地址,根据对象的布局可以得出就是虚表的地址,根据这个地址可以把虚表的第一个内存单元的内容取出,然后强制转换成一个函数指针,利用这个函数指针来访问虚函数。又因为虚表是连续的,利用每次 1可以来访问下一个内存单元。

如果对代码不理解的话,可以看这幅图就会懂了

注意:虚函数表在最后会有一个结束标志,为1说明还有虚表,为0表示没有虚表了 。(编译器不同,结束标志可能存在差异)

下面,将分别具体说明“无虚函数覆盖”和“有虚函数覆盖”时的虚函数表的情况。

(一)无虚函数覆盖

没有任何的继承,虚函数表如下图

根据示意图,编写的代码如下图所示:

和上一个程序一样,根据取出虚表里面的地址强制转换成函数指针,同样,利用虚表的连续性,每次指针 1调用对应的虚函数。可以得出虚函数按照其声明顺序存放于虚函数表中的,子类自己的虚函数是排在父类虚函数之后的。运行结果如下图

(二)一般继承(有虚函数覆盖)

如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。如图所示:

在这个类的设计中,只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

这样就会出现虚调用

base *b = new Derive();

b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。下面我们用一个示例代码来看一下

运行结果如下,确实如我们以上分析的那样,由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代:

(三)多重继承(无虚函数覆盖)

下面我们再看看多重继承的情况

对于子类实例中的虚函数表,是下面这个样子:

从图上我们可以看到

1)每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

下面我们根据上图来实现一下

运行结果如下:

在这个程序中,子类有多个父类,因此从每个父类都继承了一个虚表,因此会有3个虚表,根据代码和运行结果会发现,排列的顺序和继承的顺序一样,子类自己的虚函数排在第一个虚表的后面。程序中没有改写虚函数 ,因此没有覆盖。同时主函数中应用的是一个二重指针,利用二维数组取每个虚函数地址。

(四)多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

下图中,我们在子类中覆盖了父类的f()函数。

子类虚函数列表如图所示

三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。

子类虚函数列表访问代码如下:

程序运行结果如下所示:

本程序中,子类重写了f函数,把所有父类里面的f函数都屏蔽了。在虚表中父类f函数的位置全部换成了子类f函数的地址。因此在输出时父类的f函数没有了,全部是子类f函数的输出。

C 虚函数表及多态内部原理详解相关推荐

  1. C++中的多态——理解虚函数表及多态实现原理

    多态及其实现原理 一.多态的概念 概念 构成条件 二.虚函数的重写 重写的定义 重写的特殊情况 override和final关键字 区分重写.重载.重定义 抽象类的概念 三.多态的实现原理 父类对象模 ...

  2. 初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。

    1.c++多态的概念以及用途. 1.1虚函数实现多态 通过基类指针只能够访问派生类的成员变量,不能够访问派生类的成员函数. 解决问题的办法:使用虚函数(virtual function),只需要在函数 ...

  3. C++虚函数表和多态

    这个视频上解释的很好,C++在调用可重写的虚函数时,通过访问虚函数表来进行,这个UP主通过解释编译后的代码把多态分析的很清楚. https://www.bilibili.com/video/BV15g ...

  4. [java] 反射和多态实现原理详解以及对比

    Table of Contents 反射和多态 多态 什么是多态 java里多态的具体用法 多态的实现原理 反射 什么是反射 反射的实现原理 反射的应用 反射的弊端 反射相关类 反射应用实例 一些问题 ...

  5. C++多态的原理(虚函数指针和虚函数表)

    C++多态的原理 (虚函数指针和虚函数表) 1.虚函数指针和虚函数表 2.继承中的虚函数表 2.1单继承中的虚函数表 2.2多继承中的虚函数表 3.多态的原理 4.总结 1.虚函数指针和虚函数表 以下 ...

  6. C++中的虚函数表介绍

            在C++语言中,当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定.因为我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有虚函数都必须有定义.通常情况下,如果我们不使 ...

  7. C++多态:多态实现原理剖析,虚函数表,评价多态,常见问答与实战【C++多态】(55)

    虚函数表 一般继承(无虚函数覆写) 一般继承( 有虚函数覆写) 静态代码发生了什么 评价多态 常见问答与实战 问答 为什么虚函数必须是类的成员函数? 为什么类的静态成员函数不能为虚函数? 为什么构造函 ...

  8. C++ 面向对象(二)多态 : 虚函数、多态原理、抽象类、虚函数表、继承与虚函数表

    目录 多态 多态的概念 多态的构成条件 虚函数 虚函数的重写 协变(返回值不同) 析构函数的重写(函数名不同) final和override final override 重载, 重写, 重定义对比 ...

  9. 9-3:C++多态之多态的实现原理之虚函数表,虚函数表指针静态绑定和动态绑定

    文章目录 (1)虚函数表 (2)多态原理 (3)静态多态与动态多态 A:静态多态 B:动态多态 (1)虚函数表 如下代码,计算此带有虚函数的类的大小 #include <iostream> ...

最新文章

  1. 深圳人均GDP过一万美元随想
  2. OC基础--OC内存管理原则和简单实例
  3. free命令输出详解
  4. Django-内置用户、权限、分组模块
  5. 编码过程中的问题总结
  6. 【QM-02】Master Data (QM主数据的设置)
  7. 关于海量数据查找排序问题
  8. linux watch 文件大小,Linux watch命令的使用
  9. 随想录(程序员和收入)
  10. linux 7 远程桌面xrdp,[转帖]CentOS7安装xrdp(windows远程桌面连接linux)
  11. vue.js学习02之vue-cli脚手架创建项目环境搭建
  12. jQuery length和size()区别总结如下:
  13. oracle库sql根据拼音查汉字,根据拼音首字母模糊查询数据库中文字段
  14. mysql建三行三列表格_制作好的表格怎样才可以成重新编辑
  15. oracle数据库lpad,Oracle数据库之oracle中的decode的使用LPAD
  16. 《生物化学与分子生物学》----绪论----听课笔记(一)
  17. 丰田造世界首辆意志控制变速自行车
  18. mysql rds备份_云数据库RDS如何进行数据备份
  19. HTTP协议状态及报文组成 - 一文通读
  20. thinkphp6 循环 视图_ThinkPHP模板循环输出Volist标签用法实例详解

热门文章

  1. Java –从列表中删除所有空值
  2. oauth2和jwt_使用具有OAuth2的Web应用程序和JWT的使用来调用API – WSO2 API Manager
  3. java rmi 使用管道_使用Java RMI时要记住的两件事
  4. JDK Bug系统浪费时间
  5. Drools可执行模型还活着
  6. 每个私有静态方法都是新类的候选人
  7. 实施自定义JMeter采样器
  8. 发现大量Java原语集合处理
  9. 创建示例HTTPS服务器以获取乐趣和收益
  10. 使用spring-session外部化Spring-boot应用程序的会话状态