文中讲述的原理是推理和探讨 , 和现实中的实现不一定完全相同 。

C++ 的 虚函数 , 编译器 会 生成一个 虚函数表 。

虚函数表, 实际上是 编译器 在 内存 中 划定 的 一块 区域, 用于存放 类 的 虚函数 的 override 实现 的 函数指针 。

就是说, 如果 类 对于 基类 的 虚函数 override 了, 那么 override 的 函数 的 函数指针 就会 被记录到 虚函数表 里 。

程序在运行时会 查找 虚函数表, 找到 override 的 函数, 然后 调用 override 的 函数, 这样就实现了 调用 子类 实现的函数, 实现了 “多态” 。

虚函数表 是一个 线性表, 这样可以快速的访问 。

访问线性表 的 时间复杂度 是 O(1)  。

相对于 普通的函数 调用, 虚函数 的 调用 多了一次 查找 虚函数表 的 工作, 相当于 多了 一次 寻址, 所以 效率会更低一点 。

C++ 编译器 会为 每个成员(类 函数 字段) 指定一个 连续的 数字编号 作为 ID 。

用 连续的 数字编号 作为 ID 是为了可以以 线性表 的 方式 快速检索 。

虚函数表 由  2 个 线性表 组成 。

等, 我下面 把 线性表 称为 数组 好了 。

虚函数表 由 2 个 数组 组成 。

数组 1 是 类表, 保存 类 的 实现函数表 的 地址 。 实现函数 就是 override 了 虚函数 的 函数 。

数组 2 是 实现函数表, 保存 类 的 实现函数 的 函数指针 。

假设有 3 个类, A 、 B 、 C , 那么 编译器 会 给 这 3 个类 分别指定 ID 为   0 、 1 、 2 。

在 数组 1 (类表) 里, 就会 3 个元素,  我们用 伪码 来表示好了 :

类表 [ 0 ] = A 类 的 实现函数表 的 地址

类表 [ 1 ] = B 类 的 实现函数表 的 地址

类表 [ 2 ] = C 类 的 实现函数表 的 地址

这样, 用 类 的 ID 作为 下标(index) 来 访问 类表, 就可以取得 该类 的 实现函数表 的 地址 。

实现函数表 我们也可以用 伪码 来表示, 假设 A 类里有 Hello() 、 Thank() 、 Goodbye()     3 个 override 了 基类 虚函数 的 实现函数, 那么, 编译器 会给 这 3 个 实现函数 分别 指定 ID 为    0 、 1 、 2 。 这里只会给 实现函数 指定 ID , 不会把 其它 普通函数 包括进来 。

实现函数表 会是这样 :

实现函数表 [ 0 ] = Hello() 的 函数地址

实现函数表 [ 1 ] = Thank() 的 函数地址

实现函数表 [ 2 ] = Goodbye() 的 函数地址

这样, 用 实现函数 的 ID 作为 下标(index) 来 访问 实现函数表, 就可以取得 这个 实现函数 的 地址 。

编译的时候, 对于 普通函数 的调用, 会直接编译成       “函数地址 -> 调用”       这样的 目标代码,

对于 虚函数, 则会编译成          “根据 当前对象 的 类 ID 和 函数 ID -> 查找 虚函数表 -> 找到 实现函数 地址 -> 调用”           这样的 目标代码 。

从上面的 原理 看到, 查找 虚函数表 本身 就需要 2 次寻址, 查找 2 个 线性表(数组) 。

同时, 也可以看到, 对于 不需要 override 的 函数, 不要 声明 为 虚函数, 因为 虚函数 会 增加 查找 虚函数表 的 时间花费, 性能 比 普通函数 调用 更低一点 。

当然, 编译器 可能会作一些 优化, 比如 对于 能在 代码中 明确判断出 对象类型 的 情况, 即使是 虚函数 调用, 也会编译成 和 普通函数 一样的 处理方式    “函数地址 -> 调用” , 即 要调用的 函数地址 在 编译时就确定了 。

那什么是 编译时不能确定 对象类型 的 情况 ?   比如 工厂方法 。

编译时, 对于 虚函数, 编译器 会 检查 类 是否 进行了 override, 如果 override 了, 则 将 实现函数 列入 虚函数表, 如果没有 override, 就 查找 上一层 父类 是否 override 了, 如果 override 了, 则 将 实现函数 列入 虚函数表, 如果没有 override, 就 继续 查找 上一层 父类, 以此递推, 直到 声明 这个 虚函数 的 父类 。 如果在整个继承层级中都没有 override 这个 虚函数, 则 不会将这个 虚函数 列入 虚函数表, 当然 也不会给 这个 虚函数 指定 虚函数 ID 。所有 子类 对象 对 这个 虚函数 的 调用 会被编译成     “声明这个虚函数的 父类 里 这个虚函数 的 函数地址 -> 调用”     方式, 这种情况 和 普通函数 是一样的了 。

我们再来谈谈 “后期绑定” 。

我们先说说 “动态绑定” 。 在 Javascript 里, 对象 和 函数 可以 任意 的 绑定, 所以叫 “动态绑定” 。

对于 查找 虚函数表 的 做法, 是在 运行时 才决定具体要调用的 函数, 相当于 运行时 才 给 对象 绑定 函数, 所以叫 “后期绑定”   (我印象中好像是这么叫的)  。

漫谈 C++ 虚函数 的 实现原理的更多相关文章

  1. C++ 之虚函数的实现原理

    c++的多态使用虚函数实现,通过“晚绑定”,使程序在运行的时候,根据对象的类型去执行对应的虚函数. C++ 之虚函数的实现原理 带有虚函数的类,编译器会为其额外分配一个虚函数表,里面记录的使虚函数的地 ...

  2. C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序 ...

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

    1 C++中虚函数的作用和多态 虚函数: 实现类的多态性 关键字:虚函数:虚函数的作用:多态性:多态公有继承:动态联编 C++中的虚函数的作用主要是实现了多态的机制.基类定义虚函数,子类可以重写该函数 ...

  4. C++虚函数实现多态原理(转载)

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

  5. C++多态性:虚函数的调用原理

    多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的对象),当调用函数时可以自动判断调用的是哪个对象的函数. 一个函数说明为虚函数,表明在继承的类中重载这个函数时 ...

  6. 虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

    #include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout < ...

  7. C++虚函数工作原理

    一.虚函数的工作原理      虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table poi ...

  8. C++多重继承分析——《虚继承实现原理(虚继承和虚函数)》

    博客转载:https://blog.csdn.net/longlovefilm/article/details/80558879 一.虚继承和虚函数概念区分 虚继承和虚函数是完全无相关的两个概念. 虚 ...

  9. C++中的多态与虚函数的内部实现

    1.什么是多态         多态性可以简单概括为“一个接口,多种行为”.         也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可 ...

随机推荐

  1. C#正则表达式MatchCollection类

    认识MatchCollection 类 表示通过以迭代方式将正则表达式模式应用于输入字符串所找到的成功匹配的集合. 命名空间:  System.Text.RegularExpressions 属性:C ...

  2. 1.横向滚动条,要设置两个div包裹. 2. 点击切换视频或者图片. overflow . overflow-x

    1.横向滚动条. div.1 > div.2 > img img  img 第一: 设置 div.1 一个固定的宽度 和高度  . 例如宽度 700px;  高度是 120px; 设置 o ...

  3. centos6.6安装hadoop-2.5.0(四、hadoop HA安装)

    操作系统:centos6.6 环境:selinux disabled:iptables off:java 1.8.0_131 安装包:hadoop-2.5.0.tar.gz HA模式下的HADOOP完 ...

  4. django面试四

    Django的优点 功能完善.要素齐全:自带大量常用工具和框架(比如分页,auth,权限管理), 适合快速开发企业级网站. 完善的文档:经过十多年的发展和完善,Django有广泛的实践案例和完善的在线 ...

  5. SQL注入之Sqli-labs系列第二十八关(过滤空格、注释符、union select)和第二十八A关

    开始挑战第二十八关(Trick with SELECT & UNION) 第二十八A关(Trick with SELECT & UNION) 0x1看看源代码 (1)与27关一样,只是 ...

  6. Power BI新主页将使内容的导航和发现变得轻而易举!

    微软Power BI 将在近日发布Power BI Home登陆页面的公开预览以及Power BI服务中的新全局搜索功能.登录页将成为所有内容的一站式集合,并提供更快捷的方式来分享你的仪表板.原来在左 ...

  7. 数据库和redis的一致性

    之前的讲解,主要是在讲解redis如何支撑海量数据.高并发读写.高可用服务的架构 从这一讲开始,正式开始做业务系统的开发 商品详情页,缓存架构,90%是大量的业务(没有什么级数含量),10%最有级数含 ...

  8. jQuery的位置信息

    <head> <meta charset="UTF-8"> <title>jquery的位置信息</title> <style ...

  9. Linux按照时间顺序列出文件

    按照递增时间顺序列出所有文件 ls -ltr -l表示列出长串数据,-t表示按照时间顺序,-r表示将排序的结果反向输出 按照时间递减的顺序列出所有文件 ls -lt

  10. 服务器安装wordpress,搭建自己的博客平台

    自己构造网站的话,建立一个简单的网页还可以(比如,yongjieshi.com),对于建立复杂的博客就需要借助第三方的工具,常见的有wordpress,在阿里云上安装wordpress,我主要参考了这 ...