【C++】———— 多态

news2024/9/30 7:24:14

 9efbcbc3d25747719da38c01b3fa9b4f.gif

                                                      作者主页:     作者主页

                                                      本篇博客专栏:C++

                                                      创作时间 :2024年7月8日

9efbcbc3d25747719da38c01b3fa9b4f.gif

一、什么是多态

什么是多态呢?通俗的来讲,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生不同的状态。

举个例子:就比如买票这个行为,成人买成人票,学生买学生票,军人优先买票,这就是一个简单的例子。

二、多态的定义和实现

1.多态构成条件

在继承中要形成多态还有两个条件:

  1. 调用时必须要通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须含有对基类的虚函数的重写

这里我们插入一个概念,关于重载与重写的概念及区别:

概念:

重载(Overloading)

重载是指在同一个作用域内,函数名字相同,但参数的类型、个数或顺序不同。

重写(Overriding)

重写发生在子类和父类之间。子类中有一个与父类中函数签名(包括函数名、参数类型和个数、返回值类型)完全相同的函数,此时子类中的这个函数就重写了父类中的函数。

重载和重写的区别

  1. 范围不同:重载发生在同一个类中,重写发生在子类和父类之间。
  2. 函数签名要求不同:重载只要求参数不同,重写要求函数签名完全相同(包括参数类型、个数、返回值类型)。
  3. 权限要求不同:重载对访问权限没有要求,重写要求子类中的重写函数不能比父类中的被重写函数有更严格的访问权限。
  4. 与虚函数的关系:重载与虚函数无关,重写的函数通常是父类中的虚函数。

下面我们接着来看多态,我们先来看一串多态的代码:

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{ 
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

注意:接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数,

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样写

2.虚函数的重写和协变

上面例子中,我们实现了虚函数的重写(覆盖):

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

虚函数重写的两个例外:

2.1协变

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用派生类虚函数返回派生类对象的指针或者引用时,称为协变。

这里不仅仅可以返回当前基类和子类的类型,还可以返回其他有继承关系的类和类型。

2.2析构函数的重写  (析构函数名统一处理成destructor)

首先,我们来看看析构函数不处理成virtual的情况

我们本义是想让p1调用Person的析构,p2先调用Person的析构在调用Student的析构,但是这里并没有调用Student的析构,只析构了父类,就可能发生内存泄漏。

这是为什么呢? 

因为这里发生了隐藏,~Person()变为 this->destructor()  ~Student()为this->destructor() 

编译器将他们两个的函数名都统一处理成了destructor,因此调用的时候只看自身的类型,是Person就调用Person的函数,是Student就调用Student的函数,根本不构成多态,这并不是我们期望的那样。

我们给析构函数添加上virtual

发现子类对象,Student对象就能正常析构了

注意:析构函数加virtual是在new场景下才需要, 其他环境下可以不用

3.重载、覆盖(重写)、隐藏(重定义)的对比 

三个概念的对比:

  1. 重载:两个函数在同一作用域,然后参数类型不同
  2. 重写(覆盖):两个函数分别在基类和派生类,返回值/参数/函数名都必须相同
  3. 重定义:两个基类和派生类的同名函数不构成重写就是重定义,函数名形同,分别在基类和派生类

4.final 和 override

添加在父类虚函数后面添加final代表不能再被重写

 final修饰类,代表不能被继承:

override代表必须要重写虚函数,如果没有重写便会报错

三、抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

注意这里的包含,只要类里面有一个有纯虚函数,就是抽象类,就无法实例化对象,间接强制派生类重写。

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

四.多态的原理

1.虚函数表

以下代码环境在X86中,涉及到的指针是4个字节

我们定义一个Base类,里面有虚函数,还有一个变量int,按照我们之前学习到了,这里Base类的大小应该是4个字节,图中却是8个字节

为什么会发生这种现象呢?

用监视窗口看一下

除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表。

其实应该叫__vftptr(多个t代表table)

我们多添加几个虚函数,看看这个表里面的内容是怎么样的 

可以发现虚函数会放到虚函数表中,普通函数不会,并且表里面的内容是一个数组,是函数指针数组

2.多态的原理 

有了虚函数表的概念,我们可以尝试通过虚函数表,去找到多态的原理

下面是测试代码

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	virtual void fun(){}
private:
	int a;
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
	int b;
};
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person p;
	Student s;
	Func(&p);
	Func(&s);
	return 0;
}

2.1虚表指针里的内容

从图中我们可以看到,在内存1里面输入&p可以找到p的地址, 因为p的第一个内容就是__vfptr,因此p的地址也是__vfptr的地址,那么我们通过__vfptr的地址就可以找到虚函数表里面的内容,因此我们在内存2里面输入__vfptr的地址,我们便找到了两个虚函数的地址。 

去找s的虚表虚函数也同理 

五、做一道题吧

 这道题选B,很难相信

首先,B类型的对象p去调用test(); test()是B类继承下来的,但是里面默认存放的this指针依然是A*,将一个B类型的指针传给A类型的指针,会发生多态,B类里面的func()是重写了A类的func()  (A类func()为虚函数,B类重写了可以不写virtual)。

注意重写的关键点,仅仅是重写了A类的实现,而前面的那些声明,依然是调用的A类的声明,因此给到的val默认值是1,调用了B类的函数实现!!! 所以输出B->1

最后:

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。

2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。

3.成年人的世界,只筛选,不教育。

4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。

5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。

最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!

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

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

相关文章

AI推荐系统落地的实现与应用

目录 一、推荐系统的基础二、推荐系统的设计与实现三、推荐系统落地的挑战四、推荐系统的成功案例五、结语 AI推荐系统近年来在各个领域得到了广泛应用&#xff0c;从电子商务到娱乐&#xff0c;再到个性化学习平台。它们通过分析用户行为、偏好和历史数据&#xff0c;为用户提…

一举跃升!Cancer Discovery修正后IF30.6!

在科学出版界&#xff0c;影响因子&#xff08;IF&#xff09;被广泛认为是衡量期刊学术影响力的重要指标。每年6月&#xff0c;科睿唯安会发布期刊引证报告&#xff08;JCR&#xff09;&#xff0c;但这并不是最终结果。在10月份&#xff0c;JCR会进行统一的更新&#xff0c;包…

HTML+CSS+JS 实现3D风吹草动效果(B站视频)

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>3D effect&…

一句话彻底搞懂Java的编译和执行过程

编译和运行可以在不同的计算机上实现。 编译阶段&#xff1a;由Javac编译器将 .Java 的源文件编译为 .class 的字节码文件&#xff1b; 运行阶段&#xff1a; jvm中Java编译器运行 .class 的字节码文件&#xff0c;运行过程中&#xff0c;类加载器从硬盘中找到该字节码文件并…

【LLM之Agent】ReAct论文阅读笔记

研究背景 论文介绍了 “ReAct” 范式&#xff0c;该范式旨在融合推理和行动的功能&#xff0c;通过让大型语言模型&#xff08;LLMs&#xff09;生成既包括言语推理轨迹又包括行动序列的输出&#xff0c;解决多种语言推理和决策任务。这种方法允许模型在与外部环境&#xff08…

WPF引入多个控件库使用

目的 设计开发时有的控件库的一部分符合我们想要的UI样式&#xff0c;另一部分来自另一个控件库&#xff0c;想把两种库的样式做一个整合在同一个控件资源上。单纯通过引用的方式会导致原有样式被覆盖。这里通过设置全局样式的方式来实现。 1.安装控件库nuget包&#xff1a;H…

万界星空科技日化行业MES解决方案

日化行业MES&#xff08;制造执行系统&#xff09;解决方案是针对日化行业特点而设计的一套全面的生产管理系统&#xff0c;旨在提高生产效率、优化资源配置、加强质量控制&#xff0c;并推动企业的数字化转型。以下是对日化行业MES解决方案的详细阐述&#xff1a; 一、MES解决…

8.5结构体嵌套结构体

代码 #include <iostream> using namespace std; #include <string>//结构体嵌套结构体//定义学生结构体 struct student {string name;int age;int score; }; //定义老师结构体 struct teacher {int id;//教师编号string name;//教师姓名int age;//教师年龄struc…

iPhone数据恢复篇:iPhone 数据恢复软件有哪些

问题&#xff1a;iPhone 15 最好的免费恢复软件是什么&#xff1f;我一直在寻找一个恢复程序来恢复从iPhone中意外删除的照片&#xff0c;联系人和消息&#xff0c;但是我有很多选择。 谷歌一下&#xff0c;你会发现许多付费或免费的iPhone数据恢复工具&#xff0c;声称它们可…

目标检测基本标注工具-labelImg安装与使用

&#x1f349;一、安装 1.1 打开conda创建虚拟环境&#x1f388; conda create -n labelImg python3.8 -y 1.2 激活labelImg虚拟环境&#x1f388; activate labelImg1.3 安装labelImg&#x1f388; pip install -i https://pypi.tuna.tsinghua.edu.cn/simple lab…

0号事件何处来?

暑假开始&#xff0c;格蠹开始对NDB调试器的新一轮升级。研发团队里&#xff0c;有几位新的面孔&#xff0c;包括远程的志愿者&#xff0c;还有新来格蠹的实习生。 本地调试Linux应用是新增的一个较大功能。为了支持这个功能&#xff0c;我们特意把本来集成在ndstub模块中的Lin…

通过一个 AI 产品的落地,掌握产品经理工作全流程

对于任何一家互联网公司来说&#xff0c;用户流失都是我们必须要关注的一个问题。 这篇文章&#xff0c;我就通过我一个预测用户流失的项目&#xff0c;带你了解一个 AI 产品从筹备到上线的全流程。 从中&#xff0c;你可以体会到 AI 产品经理的完整工作流程是什么&#xff0…

Science|N型半导体水凝胶(柔性半导体器件/柔性健康监测/导电水凝胶/柔性电子)

2024年5月2日,北京大学雷霆(Ting Lei)课题组在《Science》上发布了一篇题为“N-type semiconducting hydrogel”的论文。论文内容如下: 一、 摘要 水凝胶是一类具有可调机械性能、多样生化功能和良好离子导电性的生物界面材料,但由于缺乏半导体特性,使得水凝胶在电子学中…

气膜体育馆的空气质量控制系统智能化管理—轻空间

随着科技的不断进步&#xff0c;气膜体育馆在全球范围内得到了广泛应用。一个重要的原因是其先进的空气质量控制系统&#xff0c;这不仅提高了场馆内部环境的舒适度&#xff0c;也保障了使用者的健康安全。轻空间将详细探讨气膜体育馆的空气质量控制系统是如何实现智能化管理的…

阅读笔记——《Fuzz4All: Universal Fuzzing with Large Language Models》

【参考文献】Xia C S, Paltenghi M, Le Tian J, et al. Fuzz4all: Universal fuzzing with large language models[C]//Proceedings of the IEEE/ACM 46th International Conference on Software Engineering. 2024: 1-13.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&…

【实施】项目实施计划方案(Word原件2024)

软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施软件开发管理全套资料包清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&am…

基于Java的飞机大战游戏的设计与实现论文

点击下载源码 基于Java的飞机大战游戏的设计与实现 摘 要 现如今&#xff0c;随着智能手机的兴起与普及&#xff0c;加上4G&#xff08;the 4th Generation mobile communication &#xff0c;第四代移动通信技术&#xff09;网络的深入&#xff0c;越来越多的IT行业开始向手机…

数据结构--二叉树相关习题5(判断二叉树是否是完全二叉树 )

1.判断二叉树是否是完全二叉树 辨别&#xff1a; 不能使用递归或者算节点个数和高度来判断。 满二叉树可以用高度和节点来判断&#xff0c;因为是完整的。 但是完全二叉树前面是满的&#xff0c;但是最后一层是从左到右连续这种 如果仍然用这种方法的话&#xff0c;如下图…

亚马逊速卖通卖家必看:自养号测评策略,下单高效防关联全攻略

在跨境电商的激烈竞争中&#xff0c;自养号测评策略已成为众多卖家追求低成本、高效推广的优选路径。然而&#xff0c;其成功实施离不开一系列精心策划与严格执行的关键要素。以下是对这些核心条件的深入剖析&#xff0c;旨在指导您安全、有效地构建并运营自养号测评体系。 一、…

几行代码,优雅的避免接口重复请求!同事都说好!

往期精彩文章&#xff1a;拿客户电脑&#xff0c;半小时完成轮播组件开发&#xff01;被公司奖励500&#xff01; 背景简介 我们日常开发中&#xff0c;经常会遇到点击一个按钮或者进行搜索时&#xff0c;请求接口的需求。 如果我们不做优化&#xff0c;连续点击按钮或者进行…