Visual C++中的虚函数和纯虚函数(对比学习法之一)

news2025/1/13 0:50:34

我是荔园微风,作为一名在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/693768.html

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

相关文章

初识mysql数据库之表的约束

目录 一、表的约束的概念 二、非空约束&#xff08;空属性&#xff09; 2. 空属性的使用 三、default约束&#xff08;默认值&#xff09; 1. 默认值的含义 2. 默认值的使用 3. 默认值与非空约束的关系 3.1 默认值与非空约束的生效问题 3.2 default自动生成与not null的…

Linux 学习记录40(C++篇)

Linux 学习记录40(C/QT篇) 本文目录 Linux 学习记录40(C/QT篇)一、QT软件的使用1. 新建工程 二、C语言和C的区别1. C对C的扩充2. C对C的兼容 三、第一个C程序1. cout标准输出流对象(1.介绍&#xff1a;(2. 运算符(3. cout的使用 2. cin标准输出流对象(1.介绍&#xff1a;(3. ci…

python【爬虫】【批量下载】年报抓取

python年报爬取更新 本人测试发现&#xff0c;ju chao网的年报爬取距离我上一篇博客并没有啥变化&#xff0c;逻辑没变&#xff0c;应好多朋友的需要&#xff0c;这里补充代码 import json import osimport requestsweb_url 改成网站的域名&#xff0c;因为csdn屏蔽 def load…

深入了解cookie以及实际项目中的应用

目录 cookie的原理 cookie是不可跨域的 cookie 的属性 Cookie与Session的区别 在git中的应用 cookie的原理 什么是cookie呢&#xff1f; 众所周知&#xff1a;http都是无状态的 但随着 Web 的不断发展&#xff0c;这种 无状态 的特性出现了弊端。当你登录到一家购物网站…

RabbitMQ保证消息的可靠投递,Java实现RabbitMQ消息的可靠投递,Springboot实现RabbitMQ消息的可靠投递

文章目录 一、RabbitMQ消息可靠性概述1、引出问题2、RabbitMQ消息可靠性保证的四个环节 二、保证生产者消息发送到RabbitMQ服务器1、服务端确认&#xff1a;Transaction模式&#xff08;1&#xff09;JavaAPI&#xff08;2&#xff09;springbootAPI 2、服务端确认&#xff1a;…

【Matlab】根据伯德图计算pid参数方法原理

在学习鲁棒控制的过程中&#xff0c;有一些步骤需要根据一些性能参数来计算pid参数&#xff0c;因此记录一下根据伯德图的性能来计算pid参数的原理。 系统开环响应的几个关键参数 在使用开环响应初调控制器参数时&#xff0c;主要就是调整几个需要注意的关键参数&#xff0c;…

nbcio-vue中formdesigner的组件显示不正常的处理

今天看演示系统的formdesigner组件显示不正常&#xff0c;也不知道是什么时候开始的事情&#xff0c; 如下&#xff1a; 对组件的操作倒是正常&#xff0c;但看本地是正常的&#xff0c;如下&#xff1a; 开始也不知道是什么原因&#xff0c;看代码也是一样的&#xff0c;应该…

11 MFC 制作记事本

文章目录 界面制作制作菜单设置编译框随着窗口的变化而变化OnSize打开文件文件另存为设置字体颜色修改字体文件的查找与替换查找与替换对话框显示&#xff08;非模态对话框&#xff09;对话框消息与对话框处理函数 全部代码 界面制作 制作菜单 选择Menu 点击新建 将内容写入&qu…

Nightingle夜莺Docker版SNMP监控

起因 对夜莺很感兴趣&#xff0c;想使用一下。我看官方提供了v6版本的docker-compose。而且我之前有使用过promtheus和grafana&#xff0c;虽然很好但是总觉得还是得二开。总有一天有人去搞一个不错的玩意儿出来。官方文档地址 安装与配置 直接运行docker版本的demo&#xf…

mysql内部结构和InnoDB底层原理

一、mysql内部结构 mysql总体上分为客户端、Server层、引擎层&#xff0c;具体如下图&#xff1a; 1、连接器 一般客户端通过jdbc、navicat等工具发送请求连接到mysql服务端&#xff0c;完成TCP三次握手后&#xff0c;连接器就开始认证身份&#xff0c;如果身份认证成功&…

数据结构-串、数组和广义表

数据结构之串、数组和广义表 串的定义一、串的顺序存储结构1.1、串的链式存储结构1.2、串的模式匹配算法1.2.1、Brute-Force简称为BF算法1.2.2、KMP算法 数组的定义2.1、数组的顺序存储结构2.2、数组的特点&#xff1a;结构固定-----维数和维界不变2.3、特殊矩阵的压缩存储 广义…

密码学—Kasiski测试法Python程序

Kasiski Kasiski是辅助破解Vigenere的前提工作&#xff0c;Kasiski是猜测加密者使用Vigenere密码体系的密钥的长度&#xff0c;Kasiski只是猜测长度而已&#xff0c;所以说是辅助破解Vigenere 若密文中出现两个相同的密文段(密文段的长度m>2)&#xff0c;则它们对应的明文&…

leetcode第66题:加一

题目 这是一道简单的小题&#xff0c;自己却也没写出来。。。逆序遍历数组digits&#xff0c;用carry标记当前元素是否需要进位&#xff08;0不要&#xff0c;1要&#xff09;。 若carry1&#xff0c;则当前元素要么置0&#xff0c;要么自加1。自加1之后&#xff0c;再也不需要…

【深入了解Spring Cloud Alibaba Nacos:服务注册和配置中心】—— 每天一点小知识

&#x1f4a7; 深入了解 S p r i n g C l o u d A l i b a b a N a c o s &#xff1a;服务注册和配置中心 \color{#FF1493}{深入了解Spring Cloud Alibaba Nacos&#xff1a;服务注册和配置中心} 深入了解SpringCloudAlibabaNacos&#xff1a;服务注册和配置中心&#x1f4a7;…

深入浅出解析LoRA完整核心基础知识 | 【算法兵器谱】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【算法兵器谱】栏目专注分享AI行业中的前沿/经典/必备的模型&论文&#xff0c;并对具备划时代意义的模型&论文进行全方位系统的解析&#xff0c;比如Rocky之前出品的爆款文章Make YOLO Great Again系列。也欢迎大家提…

让Ai帮我们画个粽子,它会画成什么样呢?

让Ai帮我们画个粽子&#xff0c;它会画成什么样呢&#xff1f; 本文目录&#xff1a; 一、Ai绘图技术的现状 二、看看Ai理解的粽子是怎样的 2.1、基础粽子 2.2、生成不同风格的粽子 2.2.1、真实风格的粽子 2.2.2、插图风格的粽子 2.2.3、3D风格的粽子 2.2.4、卡通风格…

Mysql锁机制介绍

Mysql锁机制 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中&#xff0c;除传统的计算资源(如CPU、RAM、I/O等)的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0…

vue-cli笔记

vue的生命周期&#xff1a; 借鉴react 钩子函数&#xff1a; change() 挂载完毕&#xff0c;vue完成模板解析&#xff0c;并把初始的真实的dom元素放入到页面后执行 beforeCreate() {// 数据代理和数据监测创建之前console.log(beforeCreate) }, created() {console.l…

深度:全面解析数据智能的金融“炼金术”!

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 金融以其财富效应&#xff0c;成为最新科技的试金石。一项新技术出来后&#xff0c;人们首先闪过的念头就是“能不能用它赚钱”。例如&#xff0c;ChatGPT带火了大模型&#xff0c;人们也开始将目标聚焦到大模型在金融领域的…

【实战】 JWT、用户认证与异步请求(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(五)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求1~56.用useAuth切换登录与非登录状态7.用fetch抽象通用HTTP请求方法&#xff0c;增强通用性8.用useHt…