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

首先,我们要搞清楚女神的所作所为,即语法规范。然后再去探究她背后的逻辑道理。她的语法说来也不复杂,概括起来就这么几条:

  1. 在类成员方法的声明(不是定义)语句前面加个单词:virtual,她就会摇身一变成为虚函数。

  2. 在虚函数的声明语句末尾中加个 =0 ,她就会摇身一变成为纯虚函数。

  3. 子类可以重新定义基类的虚函数,我们把这个行为称之为复写override)。

  4. 不管是虚函数还是纯虚函数,基类均可为其提供实现代码implementation),在这种情况下子类可以调用基类的这些实现。

  5. 子类自主选择是否要提供一份属于自己的个性化虚函数实现。

  6. 子类必须提供一份属于自己的个性化纯虚函数实现。

语法都列出来了,背后的逻辑含义是什么呢?我们用一个生动的例子来说明,虚函数是如何实现多态性的。

假设我们要设计关于飞行器的类,并且提供类似加油、飞行的实现代码,考虑具体情况,飞行器多种多样,有民航客机、歼击机、轰炸机、直升机、热气球、火箭甚至窜天猴、孔明灯、纸飞机!

假设我们有一位牛得一比的飞行员,他能给各式各样的飞行器加充不同的燃料,也能驾驶各式各样的飞行器。下面我们来看看这些类可以怎么设计。

首先,飞行器。由于我们假设所有的飞行器都有两种行为:加油飞行。因此我们可以将这两种行为抽象到一个基类中,并由它来派生具体的某款飞行器。

这是一个描述飞行器的基类,提供了两个基本的功能:加油和飞行

class aircraft

{

    void refuel(); // 加燃油,普通虚函数

    void fly()=0;  // 飞行,纯虚函数

};


这是一个普通虚函数,意味着基类希望子类提供自己的个性化实现代码,但基类同时也提供一个缺省的虚函数实现版本,在子类不复写该虚函数的情况下作为备选方案

void aircraft::refuel()

{

// 加充通用型燃油

}


这是一个纯虚函数,意味着基类强制子类必须提供自己的个性化版本,否则编译将失败。但让人惊奇的是,C++仍然保留了基类提供该纯虚函数代码实现的权利,这也许是给千变万化的实际情况留下后路

void aircraft::fly()

{

// 一种不应该被使用的缺省飞行方案

}

有了基类aircraft,我们就可以潇洒地派生出各式各样的飞行器了,比如轰炸机直升机

轰炸机类定义,复写了加油和飞行

class bomber : public aircraft

{

    void refuel(){} // 加充轰炸机的特殊燃油!

    void fly(){} // 轰炸机实弹飞行!

};

直升机类定义,复写了飞行代码,但没有复写加油

class copter: public aircraft

{

    void fly(){} // 直升机盘旋!

};

以上代码可以看到,直升机类(copter)没有自己的加油方式,直接使用了基类提供的缺省加油的方式。此时我们来定义一个能驾驭多机型的王牌飞行员类:

一个王牌飞行员

class pilot

{

    void refuelPlane(aircraft *p);

    void dirvePlane(aircraft *p);

};

给我什么飞机我就加什么油

void pilot::refuelPlane(aircraft *p)

{

    p->refuel();

}

给我什么飞机我就怎么飞

void pilot::dirvePlane(aircraft *p)

{

    p->fly();

}

很明显,我们接下来要给这位很浪的飞行员表演一下操纵各种飞行器的机会,我们来定义各种飞机然后丢给他去处理

定义两架飞机,一架轰6K,一架武直10

aircraft *H6K = new bomber;

aircraft *WZ10 = new copter;

来一个王牌飞行员,给H6K加油(加的是轰炸机特殊燃油),并且按照H6K的特点飞行

pilot Jack;

Jack.refuelPlane(H6K);  // 加充轰炸机燃油

Jack.flyPlane(H6K); // 轰炸机实弹飞行

给WZ10加油(加的是基类提供的通用燃油),按照WZ10的特点飞行

Jack.refuelPlane(WZ10); // 加充通用型燃油

Jack.flyPlane(WZ10); // 直升机盘旋

上述代码体现了最经典的所谓多态的场景,给Jack不同的飞机,就能表现不同的结果。虚函数和纯虚函数都能做到这一点,区别是,子类如果不提供虚函数的实现,那就会自动调用基类的缺省方案。而子类如果不提供纯虚函数的实现,则编译将会失败。基类提供的纯虚函数实现版本,无法通过指向子类对象的基类类型指针或引用来调用,因此不能作为子类相应虚函数的备选方案。下面给出总结。

第一,当基类的某个成员方法,在大多数情形下都应该由子类提供个性化实现,但基类也可以提供一个备选方案的时候,请将其设计为虚函数。例如飞行器的加油动作,每种不同的飞行器原则上都应该有自己的个性化的加充燃油的方式,但也不免可以有一种通用的燃油及其加充方式。

第二,当基类的某个成员方法,必须由子类提供个性化实现的时候,请将其设计为纯虚函数。例如飞行器的飞行动作,逻辑上每种飞行器都必须提供为其特殊设计的个性化飞行行为,而不应该有任何一种“通用的飞行方式”。

第三,使用一个基类类型的指针或者引用,来指向子类对象,进而调用经由子类复写了的个性化的虚函数,这是C++实现多态性的一个最经典的场景

第四,基类提供的纯虚函数的实现版本,并非为了多态性考虑,因为指向子类对象的基类指针和引用无法调用该版本。纯虚函数在基类中的实现跟多态性无关,它只是提供了一种语法上的便利,在变化多端的应用场景中留有后路

第五,虚函数和普通的函数实际上是存储在不同的区域的,虚函数所在的区域是可被覆盖(也称复写override)的,每当子类定义相同名称的虚函数时就将原来基类的版本给覆盖了,另一侧面也说明了为什么基类中声明的虚函数在后代类中不需要另加声明一律自动为虚函数,因为它所存储的位置不会发生改变。而普通函数的存储区域不会覆盖,每个类都有自己独立的区域互不相干。

最后附一幅草图以供参考

欢迎关注 林世霖 微信公众号:秘籍酷

大量技术干货,扫扫关注助力职业生涯暴击+80%

转载于:https://blog.51cto.com/vincent040/2052247

一口气搞懂《虚函数和纯虚函数》相关推荐

  1. 一个例子彻底搞懂C++的虚函数和纯虚函数

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

  2. c++ 虚函数,纯虚函数的本质区别

    转载博客:https://mp.weixin.qq.com/s?__biz=MzAxNzYzMTU0Ng==&mid=2651289202&idx=1&sn=431ffd1fa ...

  3. 虚函数和纯虚函数详解

    https://mp.weixin.qq.com/s?__biz=MzAxNzYzMTU0Ng==&mid=2651289202&idx=1&sn=431ffd1fae4823 ...

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

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

  5. C++知识点51——虚函数与纯虚函数(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146 10.练习 示例 class base { public:base() ...

  6. C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

    C++ 在继承中虚函数.纯虚函数.普通函数,三者的区别 1.虚函数(impure virtual) C++的虚函数主要作用是"运行时多态",父类中提供虚函数的实现,为子类提供默认的 ...

  7. 虚函数与纯虚函数的区别

    虚函数:为了方便使用多态特性,常常需要在基类中定义虚函数. 纯虚函数: 1.原因与虚函数相同: 2.在很多情况下,基类本身生成的对象是不合理的: 虚函数与纯虚函数的区别: 1.类里声明为虚函数的话,这 ...

  8. 虚函数和纯虚函数的区别

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

  9. C++ 虚函数和纯虚函数的区别

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

最新文章

  1. 数据库 版本号是 661,打不开。此server支持 655 和更早的版本号。不支持降级路径...
  2. mysql 时序 存储引擎_MySQL常见的三种存储引擎
  3. 判断101-200之间有多少个素数,并输出所有素数(C)
  4. PHP面试题:你所知道的php数组相关的函数?
  5. DreamweaverCS6搭建配置php本地站点(图文教程)- 教程篇
  6. Spacecom:将和信威集团在30天内决定新的收购协议
  7. Java微服务:这个画饼是个谎言,但你却不能忽视它
  8. 随机过程第二章part2
  9. Font Awesome入门教程
  10. 微信打开网页:如需浏览,请长按网址复制后使用浏览器访问怎么解决
  11. 12563 劲歌金曲
  12. 【产品】设计时可用到的认知偏差与效应
  13. 黄万里诗九首[引用]
  14. 如何使用for循环打印直角三角形
  15. 盛大创新院许式伟:影响我一生的五个重要选择
  16. 记录Android开发过程中遇到的疑难问题
  17. 2017年蓝桥杯模拟赛
  18. 货币转换 I----Python
  19. OpenGL ES 2.0 总体概述
  20. vsftp实用教程(四种配置需求)

热门文章

  1. PHP获取客户端真实IP的自定义函数
  2. Unity3D是怎么提升游戏运行效率的?
  3. SQL语句的增删改查
  4. SharePoint 2013 调用WCF服务简单示例
  5. iOS 使用Quartz 2D画虚线 .
  6. tomcat结构分析
  7. .net 转换任意类型不报错
  8. WINRAR 命令行语法
  9. python读取access_Python 获取 Access 表字段名!
  10. 二分类变量相关性分析spss_spss:两个有序分类变量的相关分析『kendallstau-b相关系数』...