Visual C++中的虚函数和纯虚函数(以外观设计模式为例)
我是荔园微风,作为一名在IT界整整25年的老兵,今天来说说Visual C++中的虚函数和纯虚函数。该系列帖子全部使用我本人自创的对比学习法。也就是当C++学不下去的时候,就用JAVA实现同样的代码,然后再用对比的方法把C++学会。
直接说虚函数和纯虚函数有很多人会直接晕,但是来看这篇帖子的很多人是有JAVA或其他面象对象编程基础的,我要不就先作个类比,究竟什么是虚函数和纯虚函数,其实很简单,初学者可以直接把C++中的虚函数和纯虚函数理解成JAVA中的抽象函数。这样是不是瞬间就明白了?
我也是在实践中发现,如果你先理解了JAVA的抽象类和抽象函数,你再理解C++的虚函数和纯虚函数就会很简单。哈哈,这个方法好吧。
那么理解了我上面说的,我们只需要再区别一下C++中的虚函数和纯虚函数理解成JAVA中的抽象函数,找寻一些他们之间的不同点就可以了。
好了,下面代码部分是本文的精华,请仔细比对下面两段代码,一段是C++,一段是JAVA,他们在实现同一个事情,输出结果也一样,但表达不同。
Facade外观模式是一种通过为多个复杂子系统提供一个一致的接口,从而使这些子系统更加容易被访问的模式。
以医院为例,就医时患者需要与医院不同的职能部门交互,完成挂号、门诊、取药等操作。为简化就医流程,设置了一个用户界面友好的接待机器的角色,患者只要在这台机器上操作,机器就能代患者完成上述就医步骤,患者则只需与接待机器交互即可。
我们先用JAVA代码来实现:
import java.util.*;interface Patient{String getName();
}interface Disposer{void dispose(Patient patient);
}class Registry implements Disposer{//挂号public void dispose(Patient patient){System.out.println("registering..."+ patient.getName());}
}class Doctor implements Disposer{//门诊public void dispose(Patient patient){System.out.println("diagnosing..."+ patient.getName());}
}class Pharmacy implements Disposer { //取药public void dispose(Patient patient){System.out.println("giving medicine..."+ patient.getName());}
}class Facade{private Patient patient;public Facade(Patient patient){this.patient=patient;}public void dispose(){Registry registry=new Registry();Doctor doctor=new Doctor();Pharmacy ph=new Pharmacy();registry.dispose(patient);doctor.dispose(patient);ph.dispose(patient);}
}class ConcretePatient implements Patient{private String name;public ConcretePatient(String name){this.name=name;}public String getName(){ return name;}
}class FacadeTest{public static void main(String[] args){Patient patient=new ConcretePatient("name");Facade f=new Facade(patient);f.dispose();
}
然后我们再用比较难的C++语言写一遍上面这个意思。
#include <iostream>
#include <string>
using namespace std:class Patient{
public:virtual string getName()=0;
};class Disposer {
public:virtual void dispose(Patient *patient)=0;
};class Registry:public Disposer{
//挂号
public:void dispose(Patient *patient){cout<<"registering..."<< patient->getName()<< endl;}
};class Doctor:public Disposer{
//门诊
public:void dispose(Patient *patient){cout <<"diagnosing..."<< patient->getName()<< endl;}
};class Pharmacy: public Disposer{
//取药
public:void dispose(Patient *patient){cout <<"giving medicine."<< patient->getName()<< endl;}
};class Facade{
private:Patient *patient;
public:Facade(Patient *patient){ this->patient=patient;}void dispose(){Registry *registry=new Registry();Doctor *doctor=new Doctor();Pharmacy *ph=new Pharmacy();registry->dispose(patient);doctor->dispose(patient);ph->dispose(patient);}
};class ConcretePatient:public Patient{
private:string name;
public:ConcretePatient(string name){this->name=name;}string getName(){return name;}
};int main(){Patient *patient=new ConcretePatient("name");Facada *f=new Facade(patient);f->dispose();return 0;
}
Java抽象类和C++虚基类的不同点
java和C++都是面向对象编程语言,遵循面向对象的特性,继承,封装,多态。由于java的抽象类和C++虚基类很像,本文对二者在这两个概念上进行一些比较。从名称上来讲,标准的概念:
C++:虚函数,虚基类;
java:抽象方法,抽象类,接口。
C++中,虚函数的存在是为了实现多态。C++中用virtual关键字来标识虚函数,即普通成员函数加上virtual就成为虚函数。Java中没有虚函数的概念,它的普通函数就相当于C++的虚函数,动态绑定是Java的默认行为。java中,如果某个方法不想被子类实现,就用final关键字使其变成非虚函数。java抽象函数/C++纯虚函数,其实就是没有方法体的方法,即一个方法只有声明,没有定义(实现)。抽象函数或者说是纯虚函数的存在是为了定义接口。
C++中纯虚函数形式为:virtual void print() = 0;
Java中纯虚函数形式为:abstract void print();
Java抽象类的存在是因为父类中既包括子类共性函数的具体定义,也包括需要子类各自实现的函数接口。抽象类中可以有数据成员和非抽象方法。抽象类中可以没有抽象方法,但具有抽象方法的类必须定义为抽象类,抽象类不能实例化。C++中抽象类只需要包括纯虚函数,就是一个抽象类。如果仅仅包括虚函数,不能定义为抽象类,因为类中其实没有抽象的概念。Java抽象类是用abstract修饰声明的类,而C++中,需要用virtual void print() = 0;的形式来标识。
Java接口用interface来定义。接口中的变量自动具有public static final属性,接口中的方法自动具有public abstract属性,接口允许多继承。接口中不能有普通成员变量,也不能具有非纯虚函数。
C++中没有接口这个概念,如果所有的方法都是纯虚函数,即全虚基类,可以将其视为和java中的接口是同等概念。这些纯虚函数必须要由子类重写,就像java中的接口中的方法必须被实现一样。
虚基类同样不能实例化。纯虚函数不能有自己的函数体,但是纯虚析构函数除外。
JAVA中的抽象函数与C++中的虚函数比较
java中没有虚函数的概念,但是有抽象函数的概念,用abstract关键字表示,java中抽象函数必须在抽象类中,而且抽象函数不能有函数体,抽象类不能被实例化,只能由其子类实现抽象函数,如果某个抽象类的子类仍是抽象类,那么该子类不需要实现其父类的抽象函数。
C++中的有虚函数的概念,用virtual 关键字来表示,每个类都会有一个虚函数表,该虚函数表首先会从父类中继承得到父类的虚函数表, 如果子类中重写了父类的虚函数(不管重写后的函数是否为虚函数),要调用哪个虚函数,是根据当前实际的对象来判断的(不管指针所属类型是否为当前类,有可能是父类型),指针当前指向的是哪种类型的对象,就调用哪个类型中类定义的虚函数。每个类只有一张虚拟函数表,所有的对象共用这张表。C++的函数多态就是通过虚函数来实现的。
C++中,如果函数不是虚函数,则调用某个函数,是根据当前指针类型来判断的,并不是根据指针所指向对象的类型。Java中,如果函数不是抽象函数,而是一个普通函数,它是默认实现类似C++中虚函数功能的,也就是说,调用某个函数,是根据当前指针所指向对象的类型来判断的,而不是根据指针类型判断。正好与C++中的普通函数相反。即JAVA里自动实现了虚函数。
纯虚函数: 主要特征是不能被用来声明对象,是抽象类,是用来确保程序结构与应用域的结构据具有直接映射关系的设计工具。带有纯虚函数的类称为抽象类,抽象类能被子类 继承使用,在子类中必须给出纯虚函数的实现,如果子类未给出该纯虚函数的实现,那么该子类也是抽象类,只有在子类不存在纯虚函数时,子类才可以用来声明对 象!抽象类也能用于声明指针或引用,或用于函数声明中。具有抽象类特性的类还有构造函数和析构函数,全部是保护的类。如果没有给出纯虚函数的实现,则在它所在的类的构造函数或析构函数中不能直接或间接的调用它。纯虚函数的实现可以在类声明外进行定义。
C++中一般都是把析构函数声明为虚函数。因为虚函数可以实现动态绑定,也就是到底调用哪个函数是根据指针当前指向哪个对象来确定的,不是根据指针的类型来确定。如果C++中不把析构函数声明为虚函数,那么其有个子类,重写了虚函数,那么当父类指针指向一个子类对象时,当调用析构函数时,只调用父类的析构函数,而无法调用子类的析构函数,所以一般情况是把析构函数声明为虚函数,实现动态绑定。当然如果一个类不包含虚函数,这经常预示不打算将它作为基类使用。当一个类不打算作为基类时,将析构函数声明为虚拟通常是个坏主意。比如:标准 string 类型不包含虚函数,如果把String作为基类继承得到子类会出问题。
总之:多态基类应该声明虚析构函数。如果一个类有任何虚函数,它就应该有一个虚析构函数;如果不是设计用于做基类或不是设计用于多态,这样的类就不应该声明虚析构函数。
关于接口与抽象类:C++中没有接口的概念,与之对应的是纯虚类,即只含有纯虚函数的类,C++抽象类的概念是含有纯虚函数成员的类。这是因为c++提供多继承,而像java、c#这些只提供单继承(避免多继承的复杂性和低效性)的语言为了模拟多继承功能就提供了接口概念,接口可以继承多个。abstract class是抽象类,至少包含一个纯虚函数的类就叫做抽象类。但是如果一个类,所有的成员都是纯虚函数,那么它和一般的抽象类在用法上是有区别的。至少microsoft给的com接口定义全部都是仅由纯虚函数构成的类。因此把这样的类定义叫做纯虚类也不算错。 纯虚函数和虚函数的区别在于前者不包含定义,而后者包含函数体。 那么纯虚类就是不包含任何实现(包括成员函数定义和成员变量定义。前者代表算法,后者代表结构)。不包含任何算法和结构的类叫做纯虚类,应该没有问题。
在java里面的确没有纯虚类的概念,因为java里没有纯虚函数这个概念。java管虚函数叫做abstract function,管抽象类叫做abstract class,直接说来,java根本没有virtual这个关键字,都用abstract代替,因此java里面根本就没有pure这个概念。有那就是interface。在interface里面定义的函数都不能有函数体,这个在java里面叫做接口。那么c++里面与interface等同的概念就是纯虚类了,c++用纯虚类来模拟interface这个抽象概念,因此这里说的“纯虚类”与java的abstract class不同,与c++的一般抽象类也不同。“纯虚类”与c++一般抽象类的区别就好比java里面interface 和 abstract class的区别。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。抽象类是不能定义对象的。
总结
C++中的虚函数就是JAVA中的普通函数, C++ 中的纯虚函数就是JAVA中的抽象函数, C++ 中的抽象类就是JAVA中的抽象类, C++ 中的虚基类就是JAVA中的接口。
作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。
相关文章:

Visual C++中的虚函数和纯虚函数(以外观设计模式为例)
我是荔园微风,作为一名在IT界整整25年的老兵,今天来说说Visual C中的虚函数和纯虚函数。该系列帖子全部使用我本人自创的对比学习法。也就是当C学不下去的时候,就用JAVA实现同样的代码,然后再用对比的方法把C学会。 直接说虚函数…...
电子元器件选型与实战应用—01 电阻选型
大家好, 我是记得诚。 这是《电子元器件选型与实战应用》专栏的第一篇文章,今天的主角是电阻,在每一个电子产品中,都少不了电阻的身影,其重要性不言而喻。 文章目录 1. 入门知识1.1 基础1.2 常用品牌1.3 电阻的种类2. 贴片电阻标识2.1 三位数标注法2.2 四位数标注法2.3 小…...

javascript 模板引擎
使用场景 在实际开发中,一般都是使用动态请求数据来更新页面,服务器端通常返回json格式的数据,正常操作是我们手动的去拼装HTML,但麻烦且容易出错,因此出现了一些用模版生成HTML的的框架叫js模板引擎如:jq…...

【数据结构】带头+双向+循环链表(DList)(增、删、查、改)详解
一、带头双向循环链表的定义和结构 1、定义 带头双向循环链表,有一个数据域和两个指针域。一个是前驱指针,指向其前一个节点;一个是后继指针,指向其后一个节点。 // 定义双向链表的节点 typedef struct ListNode {LTDataType dat…...

接口自动化测试平台
下载了大神的EasyTest项目demo修改了下<https://testerhome.com/topics/12648 原地址>。也有看另一位大神的HttpRunnerManager<https://github.com/HttpRunner/HttpRunnerManager 原地址>,由于水平有限,感觉有点复杂~~~ 【整整200集】超超超…...

【物联网】微信小程序接入阿里云物联网平台
微信小程序接入阿里云物联网平台 一 阿里云平台端 1.登录阿里云 阿里云物联网平台 点击进入公共实例,之前没有的点进去申请 2.点击产品,创建产品 3.产品名称自定义,按项目选择类型,节点类型选择之恋设备,联网方式W…...

PKG内容查看工具:Suspicious Package for Mac安装教程
Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用,Suspicious Package Mac版支持查看全部包内全部文件,比如需要运行的脚本,开发者,来源等等。 suspicious package mac使用简单,只需在选择pkg安…...
第16节:R语言医学分析实例:肺切除手术的Apriori关联规则分析
关联规则 肺切除手术的Apriori关联规则分析。 分析的目的是确定患有肺癌并需要接受肺切除术的患者的共病症状。 了解哪些症状是共病的可以帮助改善患者护理和药物处方。 分析类型是关联规则学习,通过探索变量之间的关联或频繁项集,尝试在大型数据集中找到见解和隐藏关系(H…...

ChatGPT+MidJourney 3分钟生成你的动画故事
chatgpt是真的火了,chatgpt产生了一个划时代的意义——自chatgpt起,AI是真的要落地了。 chatgpt能做的事情太多了,多到最初开发模型的程序员自己,也没法说得清楚chatgpt都能做啥,似乎只要你能想得到,它都有…...
在CSDN学Golang云原生(Kubernetes Pod调度)
一,NodeSelector定向调度 在 Kubernetes 中,可以使用 NodeSelector 字段来指定 Pod 调度到哪些节点上运行。NodeSelector 是一个键值对的 map,其中键是节点的标签名,值是标签值。具体步骤如下: 在节点上添加标签 首…...

Rust vs Go:常用语法对比(七)
题图来自 Go vs Rust: Which will be the top pick in programming?[1] 121. UDP listen and read Listen UDP traffic on port p and read 1024 bytes into buffer b. 听端口p上的UDP流量,并将1024字节读入缓冲区b。 import ( "fmt" "net&qu…...

【HarmonyOS】API6使用storage实现轻量级数据存储
写在前面 本篇内容基于API6 JS语言进行开发,通过结合轻量级数据存储开发指导的文档,帮助大家完成一个实际的代码案例,通过这个小案例,可以实现简单数据的存储。 参考文档:文档中心 1、页面布局 首先我们编写一个简单…...

Python Flask构建微信小程序订餐系统 (十二)
🔥 创建切换商品分类状态的JS文件 🔥 ; var food_act_ops={init:function(){this.eventBind();},eventBind:function(){//表示作用域var that = this;$(".wrap_search select[name=status]").change(function(){$(".wrap_search").submit();});$(&qu…...

C++——模板的作用2:特例化
目录 模板的形式: 一.模板的多参数应用: 例: 错误使用1:使用不标准的模板形参表 编辑 错误使用2:使用变量作为实参传递给函数模板 二.模板的特例化: 类模板: 针对模板的特化步骤&am…...

Python Web开发技巧VII
目录 装饰器inject_serializer 装饰器atomic rebase git 清理add的数据 查看git的当前工作目录 makemigrations文件名称 action(detailTrue, methods["GET"]) 如何只取序列化器的一个字段进行返回 Response和JsonResponse有什么区别 序列化器填表和单字段如…...

LaTex4【下载模板、引入文献】
下载latex模板:(模板官网一般都有,去找) 我这随便找了一个: 下载得到一个压缩包,然后用overleaf打开👇: (然后改里面的内容就好啦) 另外,有很多在线的数学公式编辑器&am…...

【VSCode部署模型】导出TensorFlow2.X训练好的模型信息
参考tensorflow2.0 C加载python训练保存的pb模型 经过模型训练及保存,我们得到“OptimalModelDataSet2”文件夹,模型的保存方法(.h5或.pb文件),参考【Visual Studio Code】c/c部署tensorflow训练的模型 其中“OptimalModelDataSet2”文件夹保…...

windows环境下,安装elasticsearch
目录 前言准备安装 jdk 安装nodejsElasticSearch下载ElasticSearch-head 下载 安装ElasticSearch安装ElasticSearch-head插件设置用户名密码访问ElasticSearch 默认用户名和密码参考 前言 win10elasticsearch 8.9.0 准备 安装 jdk ElasticSearch 是基于lucence开发的&#…...
Elasticsearch入门笔记(一)
环境搭建 Elasticsearch是搜索引擎,是常见的搜索工具之一。 Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析…...

记一次安装nvm切换node.js版本实例详解
最后效果如下: 背景:由于我以前安装过node.js,后续想安装nvm将node.js管理起来。 问题:nvm-use命令行运行成功,但是nvm-list显示并没有成功。 原因:因为安装过node.js,所以原先的node.js不收n…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...