本期我们学习的是 C++ 中的虚函数。

过去的几期,我们一直在讨论类、面向对象编程、继承这些内容,所有的这些内容,包括本期我们将要学习的虚函数,对整个面向对象的概念都非常重要。

虚函数能干什么呢?

虚函数允许我们在子类中重写方法。

假设我们有两个类 A 和 B,B 是 A 派生出来的,也就是 B 是 A 的子类。如果我们在 A 中创建一个方法,标记为 virtual,我们可以选择在 B 类中重写那个方法,让它做其他的事情。

像之前一样,我们通过一个例子来解释今天的知识点。

例子时间

我创建了两个类,一个是 Entity ,它唯一拥有的是一个名为 GetName 的公共方法,它会返回一个字符串,我们让它返回 “Entity”。

还有另外一个类 Player,它是 Entity 类的子集,我们增加了这个类的内容,存储了一个名字,它提供一个构造函数,允许我们指定一个名字;然后给它提供了一个叫 GetName 的方法,在这种情况下,它会返回这个名字,它的名字就是成员变量。

我们来看看如何使用上面这些设定。

我们在主函数中创造了一个 Entity,然后打印 GetName();再创建一个 Player,将这个 Player 命名为 “ganlan”,同样打印 Player 的名字。

我们可以不删除这些对象,因为程序终止后,它们自然就会被 delete 了,在这种情况下使用 delete 没什么用。

我们运行程序,结果看起来就是那样的,第一行 “Entity” ,第二行 “ganlan”。

然而,如果我们使用多态的概念,那么到目前为止我们在这里写的所有内容都有问题了。

如果我们指向一个 Player,却把它当作一个 Entity,就会遇到一些问题。

看看下面的操作。

我们创建一个名为 entity 的变量,它的类型为 Entity,它被赋值为 p,p 是上面的一个指向 Player 的指针,是一个 Player 类型,现在我却把它指向了 Entity,然后打印它的名字,你觉的结果是什么样呢?

出乎意外。

我们希望它还是Player,因为即使我们指的是这个 Entity,但它实际上是一个 Player。

可能更合适的例子是这个。

我有一个 PrintName 函数,参数是一个 Entity。

现在我们有了一个函数,它可以接受任何 Entity 类型的参数,你可以看到,我们不会得到任何的编译错误。

当我们试图将 p 传递给函数时,因为 p 是一个 Entity,Player 是 Entity,在函数里面我们做的就是调用 GetName 方法,我们期望的是,在主函数中调用的部分,参数为 Entity 类型时,GetName 用于 Entity,而参数为 Player 类型时,GetName 用于 Player。

然而,运行代码之后你会发现它打印了两次 Entity。

为什么会这样呢?

发生这种情况的原因时,在我们声明函数时,我们的方法通常在类内部起作用。然后当调用方法的时候,会调用属于该类型的方法。

我们看这个 PrintName 函数,它的参数是 Entity,这意味着当我们调用 GetName 函数时,如果是在 Entity 里面,那么它会从 Entity 类中找这个叫做 GetName 的函数。

就是这样。

然而,我们希望 C++ 能意识到一点:我在这里传递的 Entity 实际上是 Player,所以,请调用 Player 中的 GetName 函数。

这时候,虚函数就该出现了。

虚函数引入了一种叫做 Dynameic Dispatch(动态联编)的东西,它通常通过 V 表(虚函数表)来实现编译。

V 表就是一个表,它包含基类中所有虚函数的映射,这样我们可以在它运行时,将它们映射到正确的覆写(overwrite)函数。

简单起见,现在你只需要知道,如果你想覆写一个函数,必须将基类中的基函数标记为虚函数。

我们回到代码中继续看一下。

我在基类 Entity 类中 GetName 函数前面使用了 virtual 这个关键字,这可以告诉编译器,——嘿,为这个函数生成 V 表吧,这样,如果它被重写了,你可以指向正确的函数。

我们运行代码试试看。

我们得到了期望的结果。

现在,我们可以做的另一件事:使用在 C++11 引入的覆写函数标记的关键字 override。

这个不是必须的,无论有没有这个关键字,程序都会正常工作,但是我还是建议你这样做。因为首先这会让你的程序更具有可读性,阅读程序的时候我们可以知道这实际上是一个覆写的函数;它还可以帮助我们预防 Bug 的发生,比如拼写错误之类的,如果这个时候函数名称不小心写成小写了,我们就会得到一个错误,因为在基类中没有这样的函数可以覆写,或者如果我们试图覆写一个非虚函数,它也会给我们一个错误,这是可以帮助我们解决问题的方法。

最后的话

这就是虚函数的本质,但是很遗憾的一点是,虚函数并不是没有额外的开销的,有两种与虚函数相关的运行时成本。

首先,我们需要额外的内存来存储 V 表,这样我们就可以分配到正确的函数,包括基类中要有一个成员指针指向 V 表;其次,每次我们调用虚函数时,我们需要遍历这个表来确定要映射到哪个函数,这些是额外的性能损失。

由于这些成本,很多人不喜欢使用虚函数,但是我使用的过程中,没有遇到开销特别大的情况。就我个人而言,我经常使用它,可能在一些嵌入式平台上,CPU性能很差的时候需要避免使用虚函数。除此之外,我们真的不能说因为它影响性能而不去使用,因为它的影响真的很小,小到几乎可以忽略不计。

这就是虚函数,如果你有什么想法,欢迎在评论区交流,下期见。

024 - C++ 虚函数相关推荐

  1. C++ 虚函数与存虚函数

    什么是虚函数: 虚函数 是在基类中使用关键字 virtual 声明的函数,在C++ 语言中虚函数可以继承,当一个成员函数被声明为虚函数之后,其派生类中的同名函数都自动生成为虚函数, 虚函数主要体验C+ ...

  2. 【C++】多态(早期绑定、后期绑定)、抽象类(纯虚函数)、虚析构函数

    我们都知道面向对象编程的三大特征是封装.继承.多态,今天我们就来说一下其中之一的多态. 概念: 多态: 多态字面意思就是多种形态,C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同 ...

  3. C#中虚函数,抽象,接口的简单说明

    虚函数:由virtual声明,它允许在派生类中被重写,要重写方法,必须先声名为virtual public class myclass { public virtual int myint() { 函 ...

  4. 提高C++性能的编程技术笔记:虚函数、返回值优化+测试代码

    虚函数:在以下几个方面,虚函数可能会造成性能损失:构造函数必须初始化vptr(虚函数表):虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量:内联是在编译时决定的 ...

  5. 但并不从包含函数声明的接口派生_C++的虚函数和纯虚函数

    虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数. 纯虚函数:在虚函数的基础上,在函数末尾加上 = 0. class Animal {public: virtual void Sh ...

  6. c++ 虚函数_到底什么情况下会合成默认构造函数?

    来源:https://www.cnblogs.com/QG-whz/p/4676481.html 作者:good luck 编辑:公众号[编程珠玑] 编辑注:没有构造函数的时候编译器一定会生成默认构造 ...

  7. C++——虚函数(Virtual Member Functions) 【functions语意学】

    单继承下的虚函数 虚函数的实现: 为每个有虚函数的类配一张虚函数表(virtual table),它存储该类类型信息和所有虚函数执行期的地址. 为每个有虚函数的类插入一个指针(vptr),这个指针指向 ...

  8. 一口气搞懂《虚函数和纯虚函数》

    学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...

  9. 虚函数实现的基本原理(转载)

    1.概述 每一个含有虚函数(无论是其本身就含有的,还是从基类继承过来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针.如下图所示 : 其中: B的虚函数表中存放着B:: ...

最新文章

  1. JavaScript ES6箭头函数指南
  2. Linux shell 中获取当前目录的方法
  3. Docker 常见问题汇总
  4. windows服务器系统免登录,Mac系统登录Windows服务器
  5. 推一波JAVA学习公众号
  6. thinkphp 5.0 代码执行漏洞
  7. cannot mount database in EXCLUSIVE mode
  8. “跨国视频造假窝点”曝光!这个大规模数据集,帮AI揪出99%换脸视频
  9. 如何把不同尺寸ico格式图标_iConvert Icons——图标创建转换工具
  10. Linux下 vim代码编译器的使用
  11. lh服最新服务器,LH服12月1日开发更新 着重提升服务器稳定性与职业bug修复
  12. 拓端tecdat|r语言中使用Bioconductor 分析芯片数据
  13. 【语音增强】基于matlab GUI维纳滤波语音增强【含Matlab源码 298期】
  14. 小米路由青春版-2.1.26开发版固件
  15. ctfshow web入门(一) 已完结
  16. 强化学习(1)-什么是强化学习
  17. Ansible中的条件判断、handlers
  18. stm32学习笔记——通用计时器基本原理
  19. pgpool读写分离,配置设置及调研
  20. 一加7t人脸识别_10月换新推荐:一加7T/荣耀20青春版领衔好手机

热门文章

  1. 哈夫曼树的带权路径长度和
  2. Python自动化开发学习3
  3. 实现一个状态机引擎,教你看清DSL的本质
  4. java对list集合进行分页(Stream流)
  5. 952. 按公因数计算最大组件大小 : 枚举质因数 + 并查集运用题
  6. springboot+vue企业固定资产管理系统java
  7. ElementUI(具体参考官方文档)
  8. PDF电子签名A4实现
  9. CTP开发(2)行情模块的开发
  10. 2024总统大选,成为“关乎比特币未来的公投”?背后是怎样的政治抱负?