参考:http://c.biancheng.net/view/267.html

1、说明

我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是什么呢?答案是虚函数表

关于virtual 一文中,我们详细了解了C++多态的使用方式,我们知道没有 virtual 关键子就没法使用多态

2、虚函数表

我们看一下下面的代码

class A
{
public:
int i;
virtual void func() { cout << "A func" << endl; }
virtual void func2() { cout << "A func2" << endl; }
void func3() { cout << "A func3" << endl; }
};
class B : public A
{
int j;
void func() { cout << "B func" << endl; }
void func3() { cout << "B func3" << endl; }
};
int main()
{
cout << sizeof(A) << ", " << sizeof(B); //输出 8,12
return 0;
}

在32位编译模式下,程序的运行结果是:8,12

但是如果把代码中的 virtual 删掉,则程序的运行结果为:4,8

可以发现,有了虚函数之后,类所占的存储空间比没有虚函数多了4个字节,这个4个字节就是实现多态的关键 -- 位于对象存储空间的最前端的指针,存放的是 虚函数表的地址,这个是由编译器实现的

每个带有虚函数的类(包括其子类)都有虚函数表

虚函数表中存放着虚函数的地址,注意是虚函数的地址,非虚函数不在此列

虚函数表是编译器实现的,程序运行时被载入内存,一个类的虚函数表中列出了该类的全部虚函数地址。

例如,上面代码中,类A的对象的存储空间以及虚函数表如图所示:

类B的对象的存储空间以及虚函数表,如下图所示:

多态的函数调用语句被编译成根据基类指针所指向的对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的一系列指令

3、代码示例

在上面代码的基础上

A* p = new B();
p->func(); //B func
p->func3(); //A func3
p->func2(); //A func

第二行代码执行如下:

  1. 取出 p 指针所指向的位置的前4个字节,即对象所属的类(类B)的虚函数表的地址(64位编译模式下是8个字节);
  2. 根据虚函数表的地址找到虚函数表,并在虚函数表中查找要调用的虚函数地址;
  3. 调用虚函数;

到此,我们应该不难理解,上面第二行和第三行代码执行的分别是类A和类B的方法

执行 p->func(); 找的是类B虚函数表中 func() 地址,因为类B重写了,所以保存的是类B的func()地址

而执行 p->func3(); 的时候,发现 func3() 不是虚函数,所以并没有找虚函数列表,而是直接调用的p(类A类型)的方法

同样的,执行 p->func2(); 的时候,找的也是类B的虚函数表,因为类B没有重写 func2,所以存的是类A的虚函数 func2() 的地址,所以执行了类A的 func2() 方法

虚函数表-C++多态的实现原理的更多相关文章

  1. C++ 虚函数表与多态 —— 虚函数表的内存布局

       C++面试经常会被问的问题就是多态原理.如果对C++面向对象本质理解不是特别好,问到这里就会崩. 下面从基本到原理,详细说说多态的实现:虚函数 & 虚函数表.   1. 多态的本质: 形 ...

  2. C++ 虚函数表与多态 —— 多重继承的虚函数表 & 内存布局

    多重继承的虚函数表会有两个虚表指针,分别指向两个虚函数表,如下代码中的 vptr_s_1.vptr_s_2,Son类继承自 Father 和 Mather 类,并且改写了 Father::func_1 ...

  3. C++ 虚函数表与多态 —— 继承的虚函数表 & 内存布局

    1. 使用继承的虚函数表: 如果不涉及多重继承,每个类只有1个虚函数表,当子类继承父类后,子类可以自己改写和新增虚函数,如下图所示: 子类重写 func_1 后,子函数的 func_1 将会有新的逻辑 ...

  4. C++ 虚函数表与多态 —— 关键字 override 的用法

    override 仅能用于虚函数,他属于C++新特性,是重写覆盖的意思,他的存在仅仅是为了提高代码的可阅读性: 作用: 1. 提示程序的阅读者,这个函数是重写父类的功能. 2. 防止程序员在重写父类的 ...

  5. C++ 虚函数表与多态 —— 关键字 final 的用法

    final 字面上最终.最后.不可改变的意思,final 这个关键字在 Jave PHP C++中都有用到,其作用也基本一致. C++中的 final 是C++11新增,他可以用来修饰类,让类无法被继 ...

  6. C++ 虚函数表与多态 —— 多态的简单用法

    首先看下边的代码,先创建一个父类,然后在来一个继承父类的子类,两个类中都有自己的 play() 方法,在代码的第35-37行,创建一个父类指针,然后将子类地址引用赋值给父类,这时调用 P 指针的 pl ...

  7. 深入剖析C++多态、VPTR指针、虚函数表

    在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...

  8. 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数

    一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...

  9. C++虚函数表原理

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

随机推荐

  1. MySQL性能分析show profiles详解

    前言 前几篇文章我们讲了什么是 MySQL 索引,explain分析SQL语句是否用到索引,以及索引的优化等一系列的文章,今天我们来讲讲Show profiles,看看SQL耗时到底出现在哪个环节. ...

  2. 分布式零基础之--分布式CAP理论

    研究到分布式系统CAP理论,记录下来下回详细分析它: CAP是指三个单词的简称 C: 一致性(Consistence) 所有节点访问的都是同一份最新的数据副本. A: 可用性(Availability ...

  3. i5 11300H 怎么样 相当于什么水平

    i5-11300H 为 4 核 8 线程,主频 3.1GHz,睿频 4.4GHz,三级缓存 8MBi5-11300H 怎么样看完你就知道了 https://list.jd.com/list.html?

  4. Java 8 之前的时间处理

    最近在自己瞎折腾,有一个需求是计算当前日期到指定日期有多少天,用于实现纪念日或倒计时的功能.查阅资料后发现Java 8之前的时间日期处理很是麻烦而且被频频吐槽,以至于后来在 Java 8 中推出了一个 ...

  5. 【SpringMVC】SpringMVC 响应数据

    SpringMVC 响应数据 文章源码 返回值分类 返回值是字符串 Controller 方法返回字符串可以指定逻辑视图的名称,通过视图解析器解析为物理视图的地址. @Controller @Requ ...

  6. Linux学习笔记 | 常见错误之VMware启动linux后一直黑屏

    方法1: 宿主机(windows)管理员模式运行cmd 输入netsh winsock reset 然后重启电脑 netsh winsock reset命令,作用是重置 Winsock 目录.如果一台 ...

  7. nodejs中的文件系统

    . 目录 简介 nodejs中的文件系统模块 Promise版本的fs 文件描述符 fs.stat文件状态信息 fs的文件读写 fs的文件夹操作 path操作 简介 nodejs使用了异步IO来提升服 ...

  8. ORA-12560错误

    ora-12560错误是一个经典错误之一 下面我们分析一下这个错误: 产生这个错误的原因是什么呢? 1.oracle服务没有启动 Linux下查看$ps -ef | grep ora_ windows ...

  9. SDUST数据结构 - chap6 树与二叉树

    判断题: 选择题: 函数题: 6-1 求二叉树高度: 裁判测试程序样例: #include <stdio.h> #include <stdlib.h> typedef char ...

  10. C#使用struct直接转换下位机数据

    编写上位机与下位机通信的时候,涉及到协议的转换,比较多会使用到二进制.传统的方法,是将数据整体获取到byte数组中,然后逐字节对数据进行解析.这样操作工作量比较大,对于较长数据段更容易计算位置出错. ...