【C++】多态(上) 多态 | 虚函数 | 重写 | final、override | 接口继承与实现继承 | 抽象类
一、多态
概念
多态,就是多种状态,即不同的对象去完成同一个行为时会产生出不同的状态。比如:买票时,成人要原价买,学生和老人就可以享受优惠价便宜一点儿。同样是买票这个行为,不同的对象来做就有不同的状态,这就是多态的一种体现。
从代码实现上来说,多态指的是通过一个父类指针 or引用调用一个虚函数时,会根据具体对象的类型来调用该虚函数的不同实现。在多态中,相同的操作可以作用于不同的对象,而具体执行的操作则取决于对不同对象的类型判断。(“看人下菜碟🤪”)
那怎么构成多态呢?有两个条件:
1.子类重写父类的虚函数
2.通过父类的指针或引用去调用虚函数
估计你现在是一头雾水:啥是虚函数?啥是重写?多态到底是怎么用的?
不急,你现在对多态的概念一定还是一片混沌。下面我先讲解“虚函数”、“重写”的概念,然后举出多态的代码实例,你才能体会什么是多态。看到后面,再回过头来看多态的概念,会有更透彻的理解。
虚函数
先来学习一个知识点“虚函数”,注意,和菱形继承那里的 虚继承 是两个完全不同的概念!它俩的关系就是金鱼和自行车之间的关系。
🎈虚函数:被virtual修饰的类成员函数称为虚函数。
虚函数必须是非静态的成员函数,非成员函数和静态函数是无法成为虚函数的。
class Person {
public:virtual void BuyTicket() { …… }
};
虚函数的作用是:实现多态机制。
重写(覆盖)
若子类定义了一个和父类的虚函数 一模一样的虚函数,那么称子类中的虚函数重写 / 覆盖了父类的虚函数。(拥有相同的名字、返回值、形参列表)
重写是针对虚函数的概念。普通函数是没有重写的说法的。
例:
class Person
{
public:virtual void BuyTicket() {cout << "成人票10r" << endl;}
};
class Student:public Person
{
public:virtual void BuyTicket() { //子类中的虚函数重写了父类的虚函数cout << "学生票5r" << endl;}
};
其实,在重写基类虚函数时,派生类的虚函数即使 不加virtual关键字,也是可以构成重写的。
这是因为,继承后基类的虚函数被继承下来了,在派生类依旧保持虚函数属性。
但是该种写法不规范,不建议这样使用。我们还是老老实实加上virtual吧。
如何构成多态
构成多态的两个条件:1.子类重写父类的虚函数 2.通过父类的指针或引用去调用虚函数
我们已经有了“虚函数”和“重写”的知识储备,现在我来写一个多态的例子:
#include<iostream>
using namespace std;
class Person
{
public:virtual void BuyTicket() {cout << "成人票10r" << endl;}
};
class Student:public Person
{
public:virtual void BuyTicket() { //条件1.子类重写父类的虚函数cout << "学生票5r" << endl; }
};
void Pay(Person& p) { p.BuyTicket(); //条件2.通过父类的引用去调用虚函数
}
int main() {Person p;Student s;//用引用调用Pay(p);Pay(s);cout << endl;//用指针调用Person* p1 = &p, * p2 = &s; //条件2.通过父类的指针去调用虚函数p1->BuyTicket();p2->BuyTicket();return 0;
}

这,就是多态!
Q:
为什么调虚函数 一定得是父类的指针or引用?子类不行吗?
因为父类不仅能接收父类的值,还能接收子类的值;而子类仅能接收子类的值,不能接收父类的值。也就是说,父类更能包罗。
为什么一定得通过指针or引用调用?
这个问题先搁置一下。等讲到多态的底层原理时再说。
重写的例外:协变
(这个知识点实际上不常用,但是笔试题会考)
协变:是函数重写的一种特殊情况,这种情况下,父类和子类的虚函数的返回值是不同的:
父类的虚函数返回父类对象的指针or引用,子类的虚函数返回子类对象的指针or引用。这种情况就称为“协变”。
协变的前提是:协变的两个类型必须是父子关系。
例:
class Person
{
public:virtual Person& BuyTicket() { //注意返回类型Person p;cout << "成人票10r" << endl;return p;}
};
class Student:public Person
{
public:virtual Student& BuyTicket() { //注意返回类型Student s;cout << "学生票5r" << endl;return s;}
};
int main() {Person p;Student s;Person* p1 = &p, * p2 = &s;p1->BuyTicket();p2->BuyTicket();return 0;
}

既然存在返回值协变的情况,那说明构成多态的虚函数 的返回类型未必相同。这是常见的考点。
析构函数的重写
析构函数是需要重写的。
➡️为什么呢?来看下面这种特殊的情况:
//当析构函数不是虚函数,也未被重写时
class A
{
public:~A() { cout << "~A()" << endl;}
};
class B:public A
{
public:~B() {cout << "~B()" << endl;}
};
int main() {A* pa = new B; delete pa; //我希望调用的是B的析构函数return 0;
}

当我用一个父类的指针去指向子类的对象时,我希望 程序能根据对象的类型去调用对应的析构函数,也就是~B()。
但实际上,这里并没有实现多态,程序会老老实实根据pa的类型去调用~A()。而这样,由于B中部分成员未被释放,如果这部分成员涉及资源管理的话,就会导致内存泄露。
所以,析构函数得实现多态。
(这种特定情况需要记住,面试时经常会问到这个问题)
➡️但问题又来了:实现多态的前提是 子类重写父类的虚函数,而这俩的析构函数名字都不一样,怎么能重写呢?
这就不用我们担心了,编译器会出手。可以理解为,编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,我们只要加上virtual就可以。
正确的写法:
class A
{
public:virtual ~A() {cout << "~A()" << endl;}
};
class B:public A
{
public:virtual ~B() {cout << "~B()" << endl;}
};
int main() {A* pa = new B;delete pa;return 0;
}

关键字final(C++11)
final,“最终的”,表示这个就是最终版,后继无人了。
final的两个功能:1. 修饰虚函数,表示该虚函数不能再被重写 2. 修饰类,让此类不能被继承
//修饰虚函数
class A
{
public:virtual void print() final { ……}
};
class B:public A
{
public:…… //不能继承print函数
};
之前我们说过,要想一个类不能被继承,那就把它的构造函数私有化。实际上,这种方式并不好。因为从继承关系来说,它并没有直接阻止继承这种行为,子类依旧是继承了父类。只是创建创建不了子类对象。这是一种间接的阻止。
C++11引入的final,就可以直接阻止,只要父类加了final,那就是不可被继承的:
class A final
{
public:……
};
class B:public A //会报错"不能将final类类型作为基类"
{
public:……
};
关键字override(C++11)
override: 检查子类虚函数 是否正确地完成重写,如果没有则编译报错。
有时候我们会不小心把函数名写错,或者父子的参数列表顺序不一致,导致无法构成重写。这种错误编译器往往检查不出来,当运行出错误的结果时,我们去Debug半天,才无语地发现原来bug在这。
而override的出现,就是帮助我们去检查错误,确保重写的正确,免得我们调试找BUG。
class A
{
public:virtual void print() {……}
};
class B:public A
{
public:void pint() { //这里把函数名写错了,但编译器没检查出来……}
};
当加了override以后:
class B:public A
{
public:void pint() override { ……}
};

接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口。因为目的是重写,达成多态,所以只需要继承接口,函数体就用不着了。
所以说,如果不实现多态,不要把函数定义成虚函数。
重载、重写、重定义的对比

二、抽象类
概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数,纯虚函数是不需要实现的。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,只能被继承。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
class Course //课程是抽象类
{
public:virtual void TeachingMethod() = 0; //纯虚函数
};
class Math:public Course
{
public:virtual void TeachingMethod() {cout << "Math" << endl;}
};
int main() {//Course c; //报错,抽象类无法实例化出对象Math m; //派生类进行了重写,可以实例化出对象return 0;
}
纯虚函数的作用是:强制子类去完成重写。
抽象类用于表示抽象的类型。实际中,许多概念是抽象的,没有对应的实体。比如“植物” “形状”,这是笼统的概念,只有把“植物”具体到“含羞草”,把“形状”具体到“三角形”,才能定义出一个具体的类型。
相关文章:
【C++】多态(上) 多态 | 虚函数 | 重写 | final、override | 接口继承与实现继承 | 抽象类
一、多态 概念 多态,就是多种状态,即不同的对象去完成同一个行为时会产生出不同的状态。比如:买票时,成人要原价买,学生和老人就可以享受优惠价便宜一点儿。同样是买票这个行为,不同的对象来做就有不同的…...
国内怎么投资黄金,炒黄金有哪些好方法?
随着我国综合实力的不断强大,投资市场的发展也日臻完善,现已成为了国际黄金市场的重要组成部分,人们想要精准判断金市走向,就离不开对我国经济等信息的仔细分析。而想要有效提升盈利概率,人们还需要掌握国内黄金投资的…...
springboot实现数据脱敏
springboot实现数据脱敏 怎么说呢,写着写着发觉 ”这写的什么玩意“ 。 总的来说就是,这篇文章并不能解决数据脱敏问题,但以下链接可以。 SpringBoot中利用自定义注解优雅地实现隐私数据脱敏 然后回到本文,本来是想基于AOP代理&am…...
uniapp实现多时间段设置
功能说明: 1 点击新增时间,出现一个默认时间段模板,不能提交 2 点击“新增时间文本”,弹出弹窗,选择时间,不允许开始时间和结束时间同时为00:00, <view class"item_cont"> …...
uni-app - 去除隐藏页面右侧垂直滚动条
全局配置 "globalStyle": { //全局配置 "scrollIndicator":"none", // 不显示滚动条 "app-plus":{ "scrollIndicator":"none" // 在APP平台都不显示滚动条 } }局部配置 "path": "pages/ind…...
一次简单的 Http 请求异常处理 (请求的 url 太长, Nginx 直接返回 400, 导致请求服务异常)
1 结论 按照惯例直接说结论。 后台服务 A 有一个 Http 接口, 代码如下: RequestMapping(value "/user", method RequestMethod.GET) public List<UserInfoVo> getUserInfoByUserIds(RequestParam(value "userIds") List<String> userIds…...
spring Cloud在代码中如何应用,erueka 客户端配置 和 服务端配置,Feign 和 Hystrix做高可用配置
文章目录 Eureka一、erueka 客户端配置二、eureka 服务端配置 三、高可用配置FeignHystrix 通过这篇文章来看看spring Cloud在代码中的具体应用,以及配置和注解; Eureka 一、erueka 客户端配置 1、Eureka 启禁用 eureka.client.enabledtrue 2、Eurek…...
C#8.0中新语法“is {}“的介绍及使用
一、C#7.0及之前is的使用 is操作符检查表达式的结果是否与给定类型兼容,或者(从c# 7.0开始)根据模式测试表达式。有关类型测试is操作符的信息,请参阅类型测试和类型转换操作符文章的is操作符部分。 1、is 模式匹配 从C࿰…...
编译器设计01-入门概述
编译器作用概述 源代码 → 编译器 目标代码 源代码\xrightarrow{\ \ \ 编译器\ \ \ }目标代码 源代码 编译器 目标代码 编译阶段概述 编译处理包括两个阶段:前端处理和后端处理,中间过程生成语法树。 编译处理:源代码 → 语法树 …...
SpringBoot封装Elasticsearch搜索引擎实现全文检索
一、前言 注:本文实现了Java对Elasticseach的分页检索/不分页检索的封装 ES就不用过多介绍了,直接上代码: 二、实现步骤: 创建Store类(与ES字段对应,用于接收ES数据) import com.alibaba.f…...
(C)一些题4
1. 以下叙述中正确的是( )。 A.C程序中的注释只能出现在程序的开始位置和语句的后面 B.C程序书写格式严格,要求行内只能写一个语句 C,C程序书写格式自由,一个语句可以写在多行上 D.用C语言编写的程序只能放在一个程序文件中 2.设有如下程序段 char …...
ChatGPT初体验:注册、API Key获取与ChatAPI调用详解
自从2022年10月,ChatGPT诞生以后,实际上已经改变了很多!其火爆程度简直超乎想象,一周的时间用户过百万,两个月的时间用户过亿。 目前ChatGPT4已经把2023年4月以前的人类的知识都学习到了,在软件工程里面&am…...
TCP/IP协议、三次握手、四次挥手
TCP/IP TCP/IP协议分层TCP头部三次握手TCP四次挥手常见问题1、什么是TCP网络分层2、TCP为什么是三次握手,不是两次或者四次?3、TCP为什么是四次挥手,为什么不能是三次挥手将第二次挥手和第三次挥手合并?4、四次挥手时为什么TIME_W…...
Android U 匹配不到APN,无法发起数据建立的问题分析
问题 打开数据开关后,没有data PDN请求发起,因此无法上网。 根据日志确定是没有找到合适的data profile,原因一般有: 1、APN 没有配置 2、APN 类型/网络能力不满足——APN type或bearer 3、APN 配置了但被disable了——APN p…...
如何打造“面向体验”的音视频能力——对话火山引擎王悦
编者按:随着全行业视频化的演进,我们置身于一个充满创新与变革的时代。在这个数字化的浪潮中,视频已经不再只是传递信息的媒介,更是重塑了我们的交互方式和体验感知。作为字节跳动的“能力溢出”,火山引擎正在飞速奔跑…...
什么是NoOps
过去几年,自动化一直在推动整个 IT 行业向前发展。通过自动化某些任务,开发团队可以提高其能力,而无需感受到雇用新团队成员的预算压力。自动化还保证了更高的效率,特别是在操作和维护方面。 传统的软件开发工作流程涉及开发团队…...
Unity - Graphic解析
Gpahic 的作用 Graphic 是 Unity最基础的图形基类。主要负责UGUI的显示部分。 由上图可以看你出我们经常使用的Image,Text,都是继承自Graphic。 Graphic的渲染流程 在Graphic的源码中有以下属性 [NonSerialized] private CanvasRenderer m_CanvasRend…...
哈希思想的应用
目录 1.位图 位图的实现 题目变形一 题目变形二 题目变形三 总结: 2.布隆过滤器 概念 布隆过滤器的实现 3.哈希切割的思想 1.位图 哈希表和位图是数据结构中常用的两种技术。哈希表是一种数据结构,通过哈希函数把数据和位置进行映射,…...
React入门使用 (官方文档向 Part1)
文章目录 React组件:万物皆组件 JSX: 将标签引入 JavaScriptJSX 规则1. 只能返回一个根元素2. 标签必须闭合3. 使用驼峰式命名法给 ~~所有~~ 大部分属性命名!高级提示:使用 JSX 转化器 在 JSX 中通过大括号使用 JavaScript使用引号传递字符串使用大括号&…...
87基于matlab的双卡尔曼滤波算法
基于matlab的双卡尔曼滤波算法。第一步使用了卡尔曼滤波算法,用电池电压来修正SOC,然后将修正后的SOC作为第二个卡尔曼滤波算法的输入,对安时积分法得到的SOC进行修正,最终得到双卡尔曼滤波算法SOC估计值。结合EKF算法和安时积分法…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
