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

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 里, 对象 和 函数 可以 任意 的 绑定, 所以叫 “动态绑定” 。

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

最新文章

  1. 45 个非常有用的 Oracle 查询语句
  2. DB&SQL备忘
  3. centos6.8服务器部署svn
  4. Java中将一个字符串传入数组的几种方法
  5. AJAX原生JS代码
  6. xcode6.4 7.2下载地址
  7. 访问权限PPP(public、private、protected、default)之成员变量、成员变量权限解析
  8. error: device not found - waiting for device -
  9. pagefile.sys怎么删除
  10. ZOJ 2679 Old Bill(数学)
  11. JS常用方法(获取Class、获取元素样式、事件监听、cookie、ajax等)
  12. Mac之OS系统下搭建JavaEE环境 <五> 之Mysql数据库的安装及配置
  13. STL系列
  14. 爬一下国家统计局行政区划代码C#
  15. 关于python的感想和turtle作图
  16. HDU2044:一只小蜜蜂...
  17. TeamWork#3,Week5,Scrum Meeting 11.6, 11.7, 11.11, 11.12
  18. JDK5.0 特性 监控与管理虚拟机
  19. [小技巧]Mac上chrome打开触控板双指前进后退功能
  20. python 2.7中安装mysql

热门文章

  1. 关于对Access数据库的学习报告
  2. final文案+美工
  3. 20165326 java第九周学习笔记
  4. 在 子 iframe中 点击一个按钮, 变换 这个 iframe的地址url.
  5. chromium ④
  6. 深入浅出 - vue变化侦测原理
  7. HTC脚本介绍和入门示例
  8. excel idea sql 操作
  9. L2-001. 紧急救援(最短路的变形)*
  10. 基于NEO的私链(Private Blockchain)