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

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

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

  1.在类成员方法的声明(不是定义)语句前面加个单词:virtual,她就会摇身一变成为虚函数;
  2.在虚函数的声明语句末尾中加个 =0 ,她就会摇身一变成为纯虚函数;
  3.子类可以重新定义基类的虚函数,我们把这个行为称之为复写(override);
  4.不管是虚函数还是纯虚函数,基类都可以为他们提供实现(implementation),如果有的话子类可以调用基类的这些实现;
  5.子类可自主选择是否要提供一份属于自己的个性化虚函数实现;
  6.子类必须提供一份属于自己的个性化纯虚函数实现。

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

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

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

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

1 // 这是一个描述飞行器的基类,提供了两个基本的功能:加油和飞行。
2 class aircraft {
3     virtual void refuel();  // 加燃油,普通虚函数
4     virtual void fly()=0;   // 飞行,纯虚函数
5 };
1 // 这是一个普通虚函数,意味着基类希望子类提供自己的个性化实现代码,但基类
2 // 同时也提供一个缺省的虚函数实现版本,在子类不复写该虚函数的情况下作为备选方案。
3 void aircraft::refuel() {
4     // 加通用型燃油
5     std::cout << "加通用燃油" << std::endl;
6 }
1 // 这是一个纯虚函数,意味着基类强制子类必须提供自己的个性化版本,否则编译将失败。
2 void aircraft::fly() {
3     // 一种不应该被使用的缺省飞行方案
4     std::cout << "一种不应该被使用的缺省飞行方案" << std::endl;
5 }

但让人惊奇的是,C++仍然保留了基类提供该纯虚函数代码实现的权利,这也许是给千变万化的实际情况留下后路。

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

 1 // 轰炸机类定义,复写了加油和飞行
 2 class bomber : public aircraft {
 3     void refuel() {}  // 加轰炸机的特殊燃油
 4     void fly() {}     // 轰炸机实弹飞行
 5 };
 6 
 7 // 直升机类定义,复写了飞行代码,但没有复写加油
 8 class copter : public aircraft {
 9     void fly() {}  // 直升机盘旋
10 };

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

1 // 一个能驾驶各种飞行器的王牌飞行员
2 class pilot {
3     void refuelPlane(aircraft *p);
4     void dirvePlane(aircraft *p);
5 };
1 // 给我什么飞机我就加什么油
2 void pilot::refuelPlane(aircraft *p) {
3     p->refuel();
4 }
5 
6 // 给我什么飞机我就怎么飞
7 void pilot::dirvePlane(aircraft *p) {
8     p->fly();
9 }

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

1 // 定义两架飞机,一架轰6K,一架武直10
2 aircraft *H6K = new bomber;
3 aircraft *WZ10 = new copter;
1 // 来一个王牌飞行员,给H6K加油(加的是轰炸机特殊燃油),并且按照H6K的特点飞行
2 pilot Jack;
3 Jack.refuelPlane(H6K);  // 加轰炸机燃油
4 Jack.flyPlane(H6K);     // 轰炸机实弹飞行
5 
6 // 给WZ10加油(加的是基类提供的通用燃油),按照WZ10的特点飞行
7 Jack.refuelPlane(WZ10);  // 加通用型燃油
8 Jack.flyPlane(WZ10);     // 直升机盘旋

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

下面给出总结:

  1.当基类的某个成员方法,在大多数情形下都应该由子类提供个性化实现,但基类也可以提供一个备选方案的时候,请将其设计为虚函数。例如飞行器的加油动作,每种不同的飞行器原则上都应该有自己的个性化的加充燃油的方式,但也不免可以有一种通用的燃油加充方式;
  2.当基类的某个成员方法,必须由子类提供个性化实现的时候,请将其设计为纯虚函数。例如飞行器的飞行动作,逻辑上每种飞行器都必须提供为其特殊设计的个性化飞行行为,而不应该有任何一种"通用的飞行方式";
  3.使用一个基类类型的指针或者引用,来指向子类对象,进而调用经由子类复写了的个性化的虚函数,这是C++实现多态性的一个最经典的场景;
  4.基类提供的纯虚函数的实现版本,并非为了多态性考虑,因为指向子类对象的基类指针和引用无法调用该版本。纯虚函数在基类中的实现跟多态性无关,它只是提供了一种语法上的便利,在变化多端的应用场景中留有后路;
  5.虚函数和普通的函数实际上是存储在不同的区域的,虚函数所在的区域是可被覆盖(也称复写override)的,每当子类定义相同名称的虚函数时就将原来基类的版本给覆盖了,另一侧面也说明了为什么基类中声明的虚函数在后代类中不需要另加声明一律自动为虚函数,因为它所存储的位置不会发生改变。而普通函数的存储区域不会覆盖,每个类都有自己独立的区域互不相干。

转自https://blog.csdn.net/vincent040/article/details/78848322,并对代码做了小幅修正,在此感谢原作者。

转载于:https://www.cnblogs.com/chwei2ch/p/10628608.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/387115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用Caffe实现mnist的数据训练

阿里云的参考文档&#xff1a;https://help.aliyun.com/document_detail/49571.html在文档里提供了caffe的一个案例&#xff0c;利用Caffe实现mnist的数据训练。准备的数据源可以在“深度学习案例代码及数据下载”页找到Caffe数据下载并解压。要训练自己的图片&#xff0c;还是…

06 函数式編程

1 函数式编程简介 函数&#xff1a;function 函数式&#xff1a;functional 一种编程范式 特点&#xff1a; 把计算视为函数而非指令 纯函数式编程&#xff1a;不需要变量&#xff0c;没有副作用&#xff0c;测试简单 支持高阶函数&#xff0c;代码简洁 Python支持的函数式…

Android SDK开发

目前我们的应用内使用了 ArcFace 的人脸检测功能&#xff0c;其他的我们并不了解&#xff0c;所以这里就和大家分享一下我们的集成过程和一些使用心得 集成 ArcFace FD 的集成过程非常简单 在 ArcFace FD 的文档上有说明支持的系统为 5.0 及以上系统&#xff0c;但其实在 4.4 系…

jQuery WeUI 上传

jQuery WeUI 是专为微信公众账号开发而设计的一个框架&#xff0c;jQuery WeUI的官网&#xff1a;http://jqweui.com/ 需求&#xff1a;需要在微信公众号网页添加上传图片功能 技术选型&#xff1a;实现上传图片功能可选百度的WebUploader、饿了么的Element和微信的jQuery WeUI…

07 模块

模块和包的概念 等同于java中的Package 模块名文件名&#xff08;无后缀&#xff09; 在文件系統中&#xff0c;包就是文件夾&#xff0c;模块就是xxx.py文件 每层包下面都有__init__.py文件 导入模块 >>> import math >>> math.pow(2, 0.5) >>…

1.rabbitmq 集群版安装及使用nginx进行四层负载均衡设置

1.安装erlang 需要注意erlang的版本是否满足rabbitmq的需求 这里用到的版本是&#xff1a;Erlang 19.0.4 RabbitMQ 3.6.15 wget http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos.x86_64.rpmrpm -ivh erlang-19.0.4-1.el7.centos.x86_64.rpm yum -y inst…

使用WEUI uploader上传图片

使用WEUI uploader上传图片&#xff0c;博主费了很大的劲总算找到完整的了&#xff0c;并且带后台接收代码&#xff0c;有需要的朋友拿去吧&#xff0c;亲测可用&#xff01; 一、html代码<link rel"stylesheet" href"https://res.wx.qq.com/open/libs/weui/…

08 面向对象编程

1 介绍 面向对象编程是一种程序设计范式 把程序看做不同对象的相互调用&#xff0c;对现实世界建立对象模型。 面向对象编程的基本思想&#xff1a; 类和实例&#xff1a; 类用于定义抽象类型 实例根据类的定义被创建出来 2 定义类并创建实例 类通过class关键字定义&…

H5+jqweui实现手机端图片压缩上传 Base64

H5jqweui实现手机端图片压缩上传主要功能&#xff0c;使用H5的formData上传base64格式的图片&#xff0c;canvas压缩图片&#xff0c;前端样式使用weui&#xff0c;为方便起见&#xff0c;使用了jquery封装过的weui&#xff0c;jqweui。话不多少&#xff0c;开始上代码。前端代…

09 类的继承

继承一个类 class Person(object): def __init__(self, name, gender): self.name name self.gender gender class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score score 判断类型 isinstance()可以…

vue 中v-if 与v-show 的区别

相同点或者说功能&#xff0c;都可以动态操作dom元素的显示隐藏 不同点&#xff1a; 1.手段&#xff1a;v-if是动态的向DOM树内添加或者删除DOM元素&#xff1b;v-show是通过设置DOM元素的display样式属性控制显隐&#xff1b;2.编译过程&#xff1a;v-if切换有一个局部编译/卸…

vue打包后放在 nginx部署时候的配置文件

部署了三套程序,默认的&#xff0c;admin和design#user nobody; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections 1024; }http {include …

淘淘商城之技术选型、开发工具和环境、人员配置

一、技术选型 1&#xff09;Spring、SpringMVC、Mybatis 2&#xff09;JSP、JSTL、jQuery、jQuery plugin、EasyUI、KindEditor&#xff08;富文本编辑器&#xff09;、CSSDIV 3&#xff09;Redis&#xff08;缓存服务器&#xff09; 4&#xff09;Solr&#xff08;搜索&#x…

启动代码格式:nginx安装目录地址 -c nginx配置文件地址

启动启动代码格式&#xff1a;nginx安装目录地址 -c nginx配置文件地址 例如&#xff1a;[rootLinuxServer sbin]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf停止nginx的停止有三种方式&#xff1a; 从容停止1、查看进程号[rootLinuxServer ~]# ps -ef…

Lecture 3 Divide and Conquer

1.Divide the problem(instance) into one or more sub-problem; 2.Conquer each sub-problem recursively; 3.Combine solutions.

Maven报错找不到jre

富人之所以越来越富&#xff0c;穷人之所以越来越穷&#xff0c;中产阶级之所以总是在债务泥潭中挣扎&#xff0c;其主要原因之一在于他们对金钱的观念不是来自学校&#xff0c;而是来自家庭。 ---《穷爸爸富爸爸》 一、报错提示 常规配置maven环境变量&#xff0c;报错&#x…

vue按照url地址访问出错404

问题描述&#xff1a; 最近在开发cms的时候使用Vue.js框架&#xff0c;利用vue-route结合webpack编写了一个单页路由项目&#xff0c;自己在服务器端配置nginx。部署完成后&#xff0c;访问没问题&#xff0c;从页面中点击跳转也没问题&#xff0c;但是只要点击刷新或通过浏览器…

Lecture 4 Quick Sort and Randomized Quick Sort

Quick Sort --Divide and Conquer --Sorts “in place” --Very practical with tuning Divide and Conquer: 1.Divide: Partition array into 2 sub-arrays around pivot x such that elements in lower sub-array < x < elements in upper sub-array; 2.Conquer: …

HDU 3966 树链剖分后线段树维护

题意: 一棵树, 操作1.$path(a,b)$之间的点权$k$ 操作2.单点查询 题解: 树链剖分即可,注意代码细节,双向映射 主要是记录一下板子 #include <string.h> #include <stdio.h> #include <algorithm> #define endl \n #define ll long long #define ull unsigned …