11_2、多态性:虚函数

虚函数与抽象类

  • 虚函数
    • 概念
    • 声明
    • 虚析构函数
  • 抽象类
    • 纯虚函数
    • 抽象类

虚函数

概念

在赋值兼容规则中,基类类型的指针指向派生类对象时,通过此指针只能访问从基类继承来的同名成员。
如果我们希望通过指向派生类对象的基类指针,访问派生类中的同名成员该怎么办呢?
就要用到虚函数了。我们在基类中将某个函数声明为虚函数,就可以通过指向派生类对象的基类指针访问派生类中的同名成员了。这样使用某基类指针指向不同派生类的不同对象时,就可以发生不同的行为,也就实现了运行时的多态(编译时并不知道调用的是哪个类的成员)。

  • 虚函数是动态绑定的基础。记住,虚函数是非静态的成员函数,一定不能是静态(static)的成员函数。虚函数在以后我们进行软件架构设计时会起到很关键的作用。

声明

一般的虚函数声明形式为:

virtual 函数类型 函数名(形参表)
{
   函数体
}
  • 虚函数就是在类的声明中用关键字virtual限定的成员函数。以上声明形式是成员函数的实现也在类的声明中的情况。如果成员函数的实现在类的声明外给出时,则虚函数的声明只能出现在类的成员函数声明中,而不能在成员函数实现时出现,简而言之,只能在此成员函数的声明前加virtual修饰,而不能在它的实现前加

运行多态时的条件:

  1. 类之间要满足赋值兼容规则;
  2. 要声明虚函数;
  3. 通过类的对象的指针、引用访问虚函数或者通过类的成员函数调用虚函数。
#include <iostream>        
using namespace std;
class Base           // 基类Base的声明
{
public:
    virtual void show() { cout << "Base::show()" << endl; }      // 虚成员函数show
};
class Child0 : public Base     // 类Base的公有派生类Child0的声明
{
public:
    void show() { cout << "Child0::show()" << endl; }    // 虚成员函数show
};
class Child1 : public Child0   // 类Child0的公有派生类Child1的声明
{
public:
    void show() { cout << "Child1::show()" << endl; }    // 虚成员函数show
};
void CallShow(Base* pBase)     // 一般函数,参数为基类指针
{
    pBase->show();
}
int main()
{
    Base base;                 // 声明Base类的对象
    Base* pBase;             // 声明Base类的指针
    Child0 ch0;                 // 声明Child0类的对象
    Child1 ch1;                 // 声明Child1类的对象
    pBase = &base;        // 将Base类对象base的地址赋值给Base类指针pBase
    CallShow(pBase);
    pBase = &ch0;            // 将Child0类对象ch0的地址赋值给Base类指针pBase
    CallShow(pBase);
    pBase = &ch1;            // 将Child1类对象ch1的地址赋值给Base类指针pBase
    CallShow(pBase);
    return 0;
}

在这里插入图片描述

  • 类Base、Child0和Child1属于同一个类族,而且Child0是由Base公有派生的,Child1是从Child0公有派生的,所以满足赋值兼容规则,这就符合了运行时多态的第一个条件。基类Base的函数show声明为了虚函数,这是第二个条件。在CallShow函数中通过对象指针pBase来访问虚函数show,这又满足了第三个条件。这个动态绑定过程在运行时完成,实现了运行时的多态。这样通过基类指针就可以访问指向的不同派生类的对象的成员,这在软件开发中不仅使代码整齐简洁,而且也大大提高了开发效率。

总分的编程思路,只用确定基类的主要功能,然后在派生类中阐述功能特点,通过基类的函数成员访问即可。

虚析构函数

不能声明虚构造函数,而可以声明虚析构函数。

多态是指不同的对象接收了同样的消息而导致完全不同的行为,它是针对对象而言的,虚函数是运行时多态的基础,当然也是针对对象的,而构造函数是在对象生成之前调用的,即运行构造函数时还不存在对象,那么虚构造函数也就没有意义了。
析构函数用于在类的对象消亡时做一些清理工作,我们在基类中将析构函数声明为虚函数后,其所有派生类的析构函数也都是虚函数,使用指针引用时可以动态绑定,实现运行时多态,通过基类类型的指针就可以调用派生类的析构函数对派生类的对象做清理工作。
析构函数没有返回值类型,没有参数表,所以虚析构函数的声明也比较简单,形式如下:

virtual ~类名();

抽象类

抽象类的定义就是,含有纯虚函数的类。
抽象类可以为某个类族提供统一的操作接口。其特点为:

  1. 外部可以透明的使用抽象类的统一接口,而不需要知道到底是调用的抽象类的哪个派生类的成员函数。(其实这些也可以通过在基类中定义虚函数来实现)
  2. 抽象类跟一般类不同的是,它使用纯虚函数,不需要定义纯虚函数的实现。
  3. 抽象类不能实例化,即不能定义抽象类的对象,只能从它继承出非抽象派生类再实例化。

纯虚函数

虚函数在基类中不需要做任何工作,我们也要写出一个空的函数体,这时这个函数体没有什么意义,重要的是此虚函数的原型声明。
C++为我们提供了纯虚函数,让我们在这种情况下不用写函数实现,只给出函数原型作为整个类族的统一接口就可以了,函数的实现可以在派生类中给出。纯虚函数是在基类中声明的,声明形式为:

virtual 函数类型 函数名(参数表) = 0;
  • 纯虚函数的声明形式与一般虚函数类似,只是最后加了个“=0”。纯虚函数这样声明以后,在基类中就不再给出它的实现了,各个派生类可以根据自己的功能需要定义其实现。

抽象类

抽象类就是含有纯虚函数的类。抽象类可以为某个类族定义统一的接口,接口的具体实现是在派生类中给出。这种实现就具有多态特性。

  • 抽象类的派生类如果没有实现所有的纯虚函数,只给出了部分纯虚函数的实现,那么这个派生类仍然是抽象类,仍然不能实例化,只有给出了全部纯虚函数的实现,派生类才不再是抽象类并且才可以实例化。
  • 我们不能声明抽象类的对象,使用抽象类一般是通过声明抽象类的指针或引用,将指针或引用指向派生类的对象,访问派生类的成员。
#include <iostream>
using namespace std;
class Base           // 抽象类Base的声明
{
public:
    virtual void show() = 0;      // 纯虚函数成员show
};
class Child0 : public Base     // 类Base的公有派生类Child0的声明
{
public:
    void show() { cout << "Child0::show()" << endl; }    // 虚成员函数show
};
class Child1 : public Child0   // 类Child0的公有派生类Child1的声明
{
public:
    void show() { cout << "Child1::show()" << endl; }    // 虚成员函数show
};
void CallShow(Base* pBase)     // 一般函数,参数为基类指针
{
    pBase->show();
}
int main()
{
    Base* pBase;             // 声明Base类的指针
    Child0 ch0;                 // 声明Child0类的对象
    Child1 ch1;                 // 声明Child1类的对象
    pBase = &ch0;           // 将Child0类对象ch0的地址赋值给Base类指针pBase
    CallShow(pBase);
    pBase = &ch1;           // 将Child1类对象ch1的地址赋值给Base类指针pBase
    CallShow(pBase);
    return 0;
}

在这里插入图片描述

  • 这里派生类Child0和Child1的虚函数show并没有使用关键字virtual显式说明,因为Child0和Child1中的虚函数和基类Base中的纯虚函数名称一样,参数和返回值都相同,编译器会自动识别其为虚函数。
  • 上面的程序中,基类Base是抽象类,为整个类族提供了统一的外部接口。派生类Child0中给出了全部纯虚函数的实现(其实只有一个纯虚函数–show),因此不再是抽象类,可以声明它的对象。Child0的派生类Child1当然也不是抽象类。根据赋值兼容规则,基类Base的指针可以指向派生类Child0和Child1的对象,通过此指针可以访问派生类的成员,这样就实现了多态。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SAP ABAP开发过程中如何获取客户、供应商地址信息?

在SAP ERP系统中&#xff0c;在sap的应用中&#xff0c;很多地方需要用到地址和联系方式&#xff0c;sap对于地址采用了集中维护。如下图中的供应商&#xff1a;在SAP ERP系统的事务码输入栏中&#xff0c;输入事务码XK03&#xff0c;勾选地址后显示&#xff1a; 那么&#xf…

DB-Engines Ranking 2024年6月数据库排行

DB-Engines Ranking 2024年6月数据库排行 DB-Engines排名根据数据库管理系统的受欢迎程度进行排名。排名每月更新一次。 排名表 趋势图 关系型数据库前 10 名 键值数据库前 10 名 文档数据库前 10 名 时序数据库前 10 名 图数据库前 10 名 DB-Engines Ranking的分数计算方法 …

先别吹sora,ComfyUI+SVD才是你2024年必须掌握的ai视频工具!

comfyuisvd最新教程&#xff0c;没玩过ai视频的你必须要了解&#xff01; 这可能是你见过最适合小白的comfyuisvd入门教程&#xff01; 一、comfyui的特点&#xff1f; 很多人其实还不知道&#xff0c;目前市面上热门的ai视频工具有哪些&#xff1f; Sora&#xff1a;Sora目…

基于模型的理念:认知提升与研发模式转型

系统工程是从航空、航天等系统的开发过程中总结出来、用于指导复杂工程项目开展的方法论&#xff0c;是协调庞大团队完成复杂任务的技术和管理要素的综合&#xff0c;最新的国际标准将系统工程定义为“管控整个技术和管理活动的跨学科的方法&#xff0c;这些活动将一组客户的需…

【Java】如何根据应用场景选择合适的消息中间件?

一、问题解析 21.1 消息中间件的应用场景 消息中间件的应用场景主要有两个&#xff1a;异步解耦与削峰填谷。 我们首先通过电商平台用户注册送积分、送优惠券这个场景来理解异步解耦合。如果不使用消息中间件&#xff0c;电商平台送积分的实现也许是下图这个样子&#xff1a…

【AI】文心一言的使用分享

在数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术的飞速发展正在改变我们的生活。文心一言&#xff0c;作为这一浪潮中的佼佼者&#xff0c;以其卓越的自然语言处理能力和广泛的应用场景&#xff0c;给我带来了前所未有的使用体验。在这篇分享中&#xff0c;我…

新能源车用驱动器 电机电驱

硕博电子的电机电驱是以一体化动力总成为设计理念&#xff0c;整合电控、电机核心模块&#xff0c;推出的电机电控一体化动力总成。电机电控动力总成采用矢量控制算法和CAN总线通信技术&#xff0c;体积小、效率高、免维护、电磁兼容性强、方便调试&#xff0c;提高了系统的可靠…

复旦大学首本大模型中文书太厉害啦!【大模型书籍推荐】

前言 在信息爆炸的时代&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术如同璀璨的星辰&#xff0c;照亮了我们与机器沟通的道路。而今&#xff0c;复旦大学自然语言处理实验室的教授团队&#xff0c;如同航海家般&#xff0c;为我们带来了一本指引大语言模型领域前行…

【原创教程】三菱Q与MERLIN II LS激光打标机控制说明

一、控制流程说明 1.硬件连接→2.软件通讯连接→3.编写远程控制PLC程序→4.编写通讯命令。 二、硬件连接1.用RJ45口普通网线将PLC和打标机连接。 三、软件通讯连接 1.打标机侧工控机-更改操作权限-点击菜单栏Setup,在下拉菜单中,点击Level,在下一级菜单点击Supervisor(左下…

【工程实践】gradio调用模型与展示

前言 模型在云端部署好之后&#xff0c;衍生出Flask、Fastapi的接口&#xff0c;可以借助gradio调用接口展示在前端。 1.gradio代码 import gradio as gr import requests import json #调用部署的云服务接口 def greet(question):prefix_url http://0.0.0.0/get_classificat…

【Nginx系列】基于请求头的分发

文章目录 一、HTTP请求头和响应头二、基于请求头的分发2.1、基于host分发2.2、基于域名的分发测试&#xff1a;2.3、基于开发语言分发2.4、基于浏览器分发2.5、基于源IP分发 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能…

代码签名证书一年的价格是多少?如何申请

代码签名证书的价格因品牌、类型及所提供的服务等因素而有所不同&#xff0c;价格通常在数千元至万余元人民币之间不等。 不同类型代码签名证书价格差异 个人代码签名证书&#xff1a;个人代码签名证书是最基础的类型&#xff0c;适用于个体开发者&#xff0c;其价格较为经济…

【Java】解决Java报错:ArithmeticException during Division

文章目录 引言一、ArithmeticException的定义与概述1. 什么是ArithmeticException&#xff1f;2. ArithmeticException的常见触发场景3. 示例代码 二、解决方案1. 检查除数是否为零2. 使用异常处理3. 使用浮点数除法4. 使用自定义方法进行安全除法 三、最佳实践1. 始终检查除数…

香草看涨期权是什么意思?

香草看涨期权是什么意思&#xff1f; 香草期权&#xff0c;听起来或许有些异国风情&#xff0c;但实际上&#xff0c;它是金融市场中一种极为基础和普遍的交易工具。所谓的“香草”&#xff0c;在金融术语中其实是比喻一种平凡、普通但不可或缺的特质&#xff0c;正如我们日常…

基于PHP+MySQL组合开发的小程序源码系统 附带完整的安装代码包以及搭建教程

系统概述 本小程序源码系统采用PHP作为服务器端脚本语言&#xff0c;MySQL作为数据存储引擎&#xff0c;充分利用两者在Web开发领域的成熟性和灵活性。PHP以其简单、直接的语法和广泛的社区支持&#xff0c;成为快速开发Web应用的首选语言之一&#xff1b;而MySQL作为关系型数…

直到第三次才遇见懂行人,教育信息化深耕者如何选对CRM

如今&#xff0c;与诸多顶尖双一流大学交流时&#xff0c;BDRJ公司的销售们底气十足。作为一家在教育圈内知名度颇高的数字内容智能服务提供商&#xff0c;BDRJ深耕国内教育行业内容管理领域二十余年&#xff0c;是国内双软认证企业&#xff0c;中国高等教育学会教育信息化分会…

计算机组成原理(四)

在同一时刻只能有一对设备使用总线&#xff0c;会发生总线的争用 面向CPU的双总线 如果外部设备和主存之间发生信息传输&#xff0c;因为媒介是CPU&#xff0c;一样会打断CPU执行程序的任务 以存储器为中心 但是存储总线和系统总线依然不能同时使用&#xff0c;都是分时使用 …

20240612每日前端-------vue3实现聊天室(一)

先上效果图 讲讲布局设计 聊天室大致分三块&#xff1a; 左边导航右边聊天界面主界面 单独调整一下样式&#xff1a;外层friend-box先调整布局为flex&#xff0c;这样方便进行自适应布局&#xff0c;增加背景色为白色&#xff0c;设置边框圆角使得外观更加美观&#xff0c;使…

【Python入门与进阶】Jupyter Notebook配置与优化

目录 1.Jupyter Notebook简介 2.Jupyter Notebook的安装 2.1 命令行安装 2.2 可视化界面安装 3.Jupyter Notebook的使用 3.1 启动 Jupyter Notebook 3.2 Jupyter Notebook 界面介绍 3.3 创建新的 Notebook 3.4 编写和运行代码单元 3.5 使用 Markdown 编写文档 3.6 保…

[大模型]GLM-4-9B-Chat WebDemo 部署

环境准备 在autodl平台中租一个4090等24G显存的显卡机器&#xff0c;如下图所示镜像选择PyTorch–>2.1.0–>3.10(ubuntu22.04)–>12.1 接下来打开刚刚租用服务器的JupyterLab&#xff0c; 图像 并且打开其中的终端开始环境配置、模型下载和运行演示。 pip换源和安装…