Visual C++中的虚函数和纯虚函数(以策略设计模式为例)

news2024/10/6 17:19:39

我是荔园微风,作为一名在IT界整整25年的老兵,今天来说说Visual C++中的虚函数和纯虚函数。该系列帖子全部使用我本人自创的对比学习法。也就是当C++学不下去的时候,就用JAVA实现同样的代码,然后再用对比的方法把C++学会。

直接说虚函数和纯虚函数有很多人会直接晕,但是来看这篇帖子的很多人是有JAVA或其他面象对象编程基础的,我要不就先作个类比,究竟什么是虚函数和纯虚函数,其实很简单,初学者可以直接把C++中的虚函数和纯虚函数理解成JAVA中的抽象函数。这样是不是瞬间就明白了?

我也是在实践中发现,如果你先理解了JAVA的抽象类和抽象函数,你再理解C++的虚函数和纯虚函数就会很简单。哈哈,这个方法好吧。

那么理解了我上面说的,我们只需要再区别一下C++中的虚函数和纯虚函数理解成JAVA中的抽象函数,找寻一些他们之间的不同点就可以了。

好了,下面代码部分是本文的精华,请仔细比对下面两段代码,一段是C++,一段是JAVA,他们在实现同一个事情,输出结果也一样,但表达不同。

某游戏公司现要开发一款模拟游戏,该游戏主要模拟现实世界中各种鸭子的发声特征、飞行特征和外观特征。游戏需要模拟的鸭子种类及其特征如下表所示:   

鸭子种类                    发声特征                   飞行特征                           外观特征   

灰鸭(MallardDuck)     发出“嘎嘎”声(Quack)  用翅膀飞(FlyWithWings)  灰色羽毛

红头鸭(RedHeadDuck)   发出“嘎嘎”声(Quack)  用翅膀飞(FlyWithWings)  灰色羽毛、头部红色

棉花鸭(CottonDuck)   不发声(QuackNoWay)  不能飞行(FlyNoWay)    白色

橡皮鸭(RubberDuck)  橡皮声音(Squeak)    不能飞行(FlyNoWay)    黑白橡皮颜色

为支持将来能够模拟更多种类鸭子的特征,采用策略设计模式。其中,Duck 为抽象类,描述了抽象的鸭子,而类 RubberDuck、MallardDuck、CottonDuck 和RedHeadDuck分别描述具体的鸭子种类,方法fly()、quack()和display()分别表示不同种类的鸭子都具有飞行特征、发声特征和外观特征;接口FlyBehavior与QuackBehavior'分别用于表示抽象的飞行行为与发声行为;类 FlyNoWay与 FlyWithWings分别描述不能飞行的行为和用翅膀飞行的行为:类Quack,Squeak与QuackNoWay分别描述发出“嘎嘎"声的行为、发出橡皮与空气摩擦声的行为与不发声的行为。

我们先用JAVA代码来实现:

interface FlyBehavior{   
    public void fly();   
};   

interface QuackBehavior{   
    public void quack();  
};

class FlyWithWings implements FlyBehavior{   
    public void fly() {System.out.println("使用翅膀飞行!");}
};

class FlyNoWay implements FlyBehavior{   
    public void fly() {System.out.println("不能飞行!");}
};   

class Quack implements QuackBehavior {   
     public void quack() {System.out.printIn("发出嘎嘎声!"); }
};   

class Squeak implements QuackBehavior{   
     public void quack(){System.out.println("发出空气与橡皮摩擦声 !");}   
};   

class QuackNoWay implements QuackBehavior{
     public void quack() {System.out.println("不能发声!");}
}; 

abstract class Duck{   
     protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;
    public void fly() { flyBehavior.fly();};   
     public void quack() {quackBehavior.quack();} 
    public abstract void display();   
}; 

class RubberDuck extends Duck{   
      public RubberDuck(){   
           flyBehavior=new FlyNoWay();   
           quackBehavior=new Squeak();   
      }
      public void display(){/*此处省略显示橡皮鸭代码*/}
};
//其他代码省略   

然后我们再用比较难的C++语言写一遍上面这个意思。

#include<iostream>
using namespace std;

class FlyBehavior{
    public:virtual void fly()=0;
};

class QuackBehavior{
  public:virtual void quack()=0;
};

class FlyWithWings:public FlyBehavior{
  public:void fly(){cout<<"使用翅膀飞行 !" <<endl;}
};

class FlyNoWay:public FlyBehavior{
  public:void fly() { cout<< "不能飞行!"<<endl;}
};

class Quack:public QuackBehavior{
  public:void quack(){ cout<<"发出嘎嘎声 !"<<endl; }
};

class Squeak:public QuackBehavior{
  public:void quack() {cout<<"发出空气与橡皮摩擦声!"<<endl;}
};

class QuackNoWay:public QuackBehavior{
  public:void quack(){ cout<<"不能发声 !"<<endl; }
};

class Duck{
protected:
  FlyBehavior *flyBehavior;
  QuackBehavior *quackBehavior;
public:
  void fly(){flyBehavior->fly();}
  void quack(){quackBehavior->quack();}
  virtual void display()=0;
};

class RubberDuck:public Duck{
public:
  RubberDuck(){
  flyBehavior=new FlyNoWay();
  quackBehavior=new Squeak();
  }
  void display(){/*此省略显示橡皮鸭的代码*/}
};
//其他代码省略

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深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

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

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

相关文章

微信小程序 rich-text富文本框 怎么设置里面节点的样式

1、在JS中我们获取数据&#xff0c;在没有类名的情况下 使用正则匹配你想要添加演示的节点 res[1].data[0].f_content为rich-text里面的节点 如图 代码&#xff1a;让获取的节点中的图片的最大宽度为100%,高度为auto this.content res[1].data[0].f_content.replace(/\<…

数据库连接与操作怎么学习? - 易智编译EaseEditing

学习数据库连接和操作是进行数据管理和处理的关键技能之一。下面是一些建议&#xff0c;可以帮助您学习数据库连接和操作&#xff1a; 学习数据库基础知识&#xff1a; 首先&#xff0c;了解数据库的基本概念、术语和原理。掌握关系型数据库和非关系型数据库的特点以及它们之…

Spring Boot中的Elasticsearch自动配置

Spring Boot中的Elasticsearch自动配置 Elasticsearch是一个基于Lucene的分布式全文搜索引擎&#xff0c;它在搜索、分析等方面具有出色的表现。Spring Boot中的Elasticsearch自动配置为我们提供了一种快速集成Elasticsearch的方式&#xff0c;使我们可以在Spring Boot应用程序…

【Unity每日一记】常见的类你都掌握了吗,没有就过来看看吧

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

layui弹出层laydate时间选择一闪而过,无法弹出时间选择

问题&#xff1a;layUI日期框弹不出&#xff0c;一闪而过 laydate.render({elem: #ctime,type: datetime,trigger:click }); 解决方案&#xff1a;关键代码&#xff0c;添加如下代码 trigger:click 实现效果

浏览器基础原理-安全: HTTPS

HTTP协议的历史: HTTP协议的目的很单纯, 就是为了传输超文本文件, 所以早期的 HTTP 一直保持着明文传输数据的特征, 但是中间很有可能会被截取或者篡改, 即收到中间人攻击. 解析HTTP协议栈层面: HTTPS往里面加入了安全层, 它的指责是: 对发起HTTP请求的数据进行加密和对接收…

Redis实战篇(二)

三、优惠卷秒杀 3.1 全局唯一ID 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题&#xff1a; id的规律性太明显 受单表数据量的限制 场景分析一&am…

初学mybatis(二)CRUD操作及配置解析

学习回顾&#xff1a;初学mybatis&#xff08;一&#xff09; 一、namespace 1、将上面案例中的UserMapper接口改名为 UserDao&#xff1b;2、将UserMapper.xml中的namespace改为为UserDao的路径 .3、再次测试 结论&#xff1a; 配置文件中namespace中的名称为对应Mapper接口或…

Python基本操作

前言 啦啦啦&#xff0c;现在开始,打算做一期Python基础教程&#xff0c;欢迎大家来看哦&#xff01; 导读 这期文章真的是Python基础中的基础&#xff0c;相信有一定编程基础的小伙伴们都一定能看懂的… 本文共分为以下几个部分&#xff1a; 数与运算符基本输入输出注释模…

Linux环境安装openJDK

现在越来越多的开发者使用 openJDK &#xff0c;当然这也是一种趋势 官网下载&#xff1a; https://jdk.java.net/java-se-ri/8-MR5这里以 jdk8 为例 配置环境变量 看网上的教程&#xff0c;好多人都推荐去这个官网下载&#xff1a; https://hg.openjdk.org/其实这个是 O…

算法笔记——数组篇

整理一下刷过的题型&#xff0c;持续更新&#xff0c;参照代码随想录 代码随想录数组篇 二分法 力扣相关题目&#xff1a; 704. 二分查找 只适用于排序有序数组&#xff0c;且没有重复元素。主要是有两种写法&#xff0c;二分查找涉及的很多的边界条件&#xff0c;逻辑比较简单…

V-Box智能车载终端-OBUYZN2

1 产品概览 OBUYZN2型智能车载终端&#xff08;以下简称&#xff09;是 型智能车载终端&#xff08;以下简称&#xff09;是 型智能车载终端&#xff08;以下简称&#xff09;是 型智能车载终端&#xff08;以下简称&#xff09;是 组成智能网联 系统 的核心数据交互设备 &…

Java 顶层类(top-level class)的访问控制修饰符

在Java中&#xff0c;处于最外层的类就是顶层类&#xff08;top-level class&#xff09;&#xff0c;类的声明外面再没有其它的类包裹。 顶层类的访问控制修饰符只能是public、或者包访问控制修饰符&#xff08;也就是无访问控制修饰符&#xff09;。 访问控制修饰符访问范围…

用codetyphon开发一个单机版跨平台数据处理小软件

目录 1 前言 2 一种可能的方案 2.1 数据存储使用dbf格式 2.2 用Lazarus或CT开发 2.3 根据外部csv或者excel电子表格快速建表 2.4 用python汇总和审核 3 当前进度 3.1 根据excel电子表格快速自动建表和导入数据 3.2 显示数据 3.3 建立测试数据库 1 前言 现在各种现成的…

Vue天气案例

绑定事件的时候&#xff1a;xxx"yyy" yyy可以写一些简单的语句。 <body><div id"root"><h2>今天天气很{{info}}</h2><button click"changeWether">切换天气</button></div> </body><scr…

HBase-问题

最终理解HBase数据模型的关键在于稀疏、分布式、多维、排序的映射。其中映射map指代非关系型数据库的key-Value结构。 1.怎么理解稀疏&#xff1f; 不同的行有不同的列&#xff0c;这就叫稀疏 有的行有3个列&#xff0c;有的行有2个列&#xff0c;那么2个列的在显示的时候&a…

Unity之穿山甲SDK

SDK版本相关问题官网解答记录: 官网咨询工单地址记录&#xff1a; https://www.csjplatform.com/athena/user-feedback/order/list?identify_keycb790ca553ed7253d29a4dbd4041281b62c449f55d6faaeb4e744370c9b34e38&init_id652798&order_id652798

解决AntvX6的阴影残留

问题描述&#xff1a;在使用antVX6的过程中&#xff0c;有时候重选渲染会出现阴影残留。 解决&#xff1a; 每次重新渲染画布时&#xff0c;使用 clearCells API清除一下画布即可解决。 ... const graph new Graph({container: document.getElementById("app"),w…

高性能分布式缓存Redis(二) 高级应用

一、持久化原理 持久化 Redis是内存数据库&#xff0c;数据都是存储在内存中&#xff0c;为了避免进程退出导致数据的永久丢失&#xff0c;需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘&#xff1b;当下次Redis重启时&#xff0c;利用持久化文件实现数据恢…

UE4/5数字人Metahuman与Style3D的使用【一、Style3DAtelier软件制作smd格式衣服并导入ue】

目录 软件和插件下载 安装软件Style3DAtelier 放入插件 布料模拟制作&#xff1a; 导出人物 &#xff1a; 数字人与小白人 Style3D添加衣服&#xff1a; 导入小白人或数字人&#xff1a; 身高修改&#xff1a; uv调整 模拟查看情况&#xff1a; 导出smd格式&#x…