【C++】多态的使用详解

本篇要分享的内容是多态,以下为本篇目录。

目录

1.多态的概念

2. 多态的定义及实现

3.虚函数

4.C++11  override和final

4.1final关键字

4.2override关键字

 5.抽象类

5.1抽象类的概念

5.2接口继承和实现继承


1.多态的概念

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

比如旅行景点的成人票是全票,儿童半票,军人优先购票;

又比如拼多多的红包,新用户就会获得很多福利;而老用户的红包福利只有一点点。

像这样不同的身份可以产生不同的行为和结果,也是一种多态行为。

2. 多态的定义及实现

用一段简单的代码来认识多态

#include<iostream>
using namespace std;
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)。

并且在函数测试中使用父类创建了对象,并且调用函数,这时要注意我们在main函数中分别用父类和子类创建了对象,并调用了Func函数。

在之前的继承中我们知道,父类的引用即可传父类对象,也可以传子类对象

这时运行代码观察结果

 

可以看到这里调用了两个函数:
普通人买票全价,学生买票半价;

但是当我们将切片改为传值传参,而不是传引用传参时结果就会调用同一个函数

可以看到这里去掉了&符号

运行结果如下

可以看到调用到的时一个函数

那这就证明出形成多态的条件:

①.虚函数重写

②.父类的指针或者函数去调用虚函数

以上条件只要有一个不满足,就会变成普通调用,就会去看对象的类型

3.虚函数

在之前的继承中我们学到过虚继承的关键字也是virtual,但是和我们今天在多态中所学的虚函数是没有关系的,如同取地址符号&,和引用符号&,虽然符号相同,但是是有区别的。

要构建多态的第一个条件就是构成虚函数重写,那么构成虚函数重写也是有条件的:

继承关系中两个父子类关系的虚函数,函数名、参数、返回值,都要相同,才能完成虚函数的重写。

当然,virtual只能修饰成员函数,它的作用是修饰成员函数来构成多态,在类外使用当然是会直接报错的。

但是上述的三同(函数名、参数、返回值相同)又有一个例外,称之为协变:返回值可以不同,但是必须是父子类关系的指针或引用

如图

可以看到我们修改了函数的返回值是父子关系类的指针,结果也会构成多态。

但是在如上的这中情况下,子类又可以不加virtual

为什么要这样设计呢?

这里的函数需要重写,重写就是重复实现,也可以认为在父类中的虚函数,子类与父类同名的虚函数virtual也会继承下来。

以上的两种特殊用法是需要记忆的,是可以使用,但是在我们自己编写代码中最好还是使用三同来完成多态。

接下来则是析构函数,有如下场景

class Person {
public:~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:~Student() { cout << "~Student()" << endl; }
};
int main()
{Person* p1 = new Person;delete p1;Person* p2 = new Student;delete p2;return 0;
}

这里的分别在两个类中定义了两个析构函数,为方便观察我们输出他们。

再在main函数中分别定义两个指针,并且分别指向父类和子类并析构

运行结果如下 

 可以看到创建的两个指针指向的类不同,但是却同时调用了父类的析构函数,原因是在没有使用virtual修饰函数的情况下,所有的析构函数都被编译器命名为:destroy(),所以在两个不同的类中相当与重写了析构函数。

因为子类不能正常调用析构函数,所以有可能会造成内存泄漏。

 所以我们需要使用虚函数就可以使他们分别调用自己类中的析构函数

(子类调用析构函数是先子后父)。

4.C++11  override和final

在C++11中更新了两个关键字final和override;

4.1final关键字

1.final修饰类时,这个类不能被继承

2.final修饰虚函数时,这个虚函数不能被重写

用法如下

class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};

此处实在Car类中的虚函数后加上final {};

 可以看到使用final之后,这个虚函数就不能被重写了

并且这个函数必须是虚函数

 这里报错很明显,final不能修饰非虚函数。

在Car类后加上final之后这个类就不能被继承了 。

4.2override关键字

override用来修饰派生类的虚函数,用来检测是否完成重写。

以下是override的使用位置

class Car
{public:virtual void Drive(){}
};class Benz :public Car
{public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

如果没有完成重写就会报错

虚函数一定是要重写的,否则虚函数是没有意义的。

 5.抽象类

5.1抽象类的概念

上面我们提到过虚函数。在虚函数后面加上=0,则这个函数为纯虚函数

那么此时,包含整个纯虚函数的类叫做抽象类,也叫做接口类,抽象类不能实例化对象。 

可以看到上图的Car中有纯虚函数,Car就变成了抽象类,此时Car也就不能定义对象,但是可以定义指针。

 

上图中我们定义了新的类,并且继承了抽象类Car,可以看到此时新的类也不能实例化对象了; 

但是我们可以将纯虚函数重写,这样就可以实例化对象了

所以有以下使用场景

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:void Drive(){cout << "Benz" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void func(Car* c)
{c->Drive();
}
int main()
{func(new Benz);func(new BMW);
/*Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();*/
}

在以上代码中我们发现在func函数中的参数定义了抽象类的指针对象c,利用c去调用类中重写的函数。

同时在main函数中调用函数,不难看出指向哪个子类就调用的是哪个子类重写的函数。

所以我们可以得出的结论是抽象类强制了子类去重写。

5.2接口继承和实现继承

普通函数的继承是一种实现继承

虚函数的继承是一种接口继承

用代码说明

首先是实现继承,我们在父类中简单写了一个输出函数,他会直接继承到子类中,也就是说子类可以直接对func函数直接进行调用,可以理解为一种函数的复用。

而虚函数的接口继承,相当于需要在子类中重写函数的实现,但是调用的参数,或者说是接口,还是父类的接口,所以我们才需要重写虚函数。

最后用一道小题来了解接口继承

class A{public:virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}virtual void test(){ func();}};class B : public A{public:void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }};int main(int argc ,char* argv[]){B*p = new B;p->test();return 0;}A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

首先观察main函数中使用B类创建了指针p;

使用p去调用test,test在父类中,所以test中的this指针是指向A类

在test中又调用了func函数

因为func在A类中被定义为虚函数,并且在B类中重写,在加上虚函数为接口继承,

虚函数只重写函数体内容,而接口还是父类的接口

所以答案为:B->1

以上就是本篇要分享的关于多态的概念和简单实用,本人水平有限,尽管不遗余力但本篇的内容仍有不足,还请读者指正,感谢您的阅读。

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

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

相关文章

RedisInsight——redis的桌面UI工具使用实践

下载 官网下载安装。下载地址在这里 填个邮箱地址就可以下载了。 安装使用。 安装成功后开始使用。 1. 你可以add一个地址。或者登录redis cloud 去auto-discover 2 . 新增你的redis库地址。注意index的取值 3。现在可以登录到redis了。看看结果 这是现在 在服务器上执行…

AC修炼计划(AtCoder Beginner Contest 328)

传送门&#xff1a; Toyota Programming Contest 2023#7&#xff08;AtCoder Beginner Contest 328&#xff09; - AtCoder 本章对于自己的提升&#xff1a;dfs的运用&#xff0c;带权并查集&#xff0c;以及状压dp。 A&#xff0c;B&#xff0c;C题比较简单&#xff0c;直接…

虚拟机配置完NAT模式之后可以和主机ping通但是ping 百度显示:网络不可达

具体linux网络配置看这&#xff1a;http://t.csdnimg.cn/KRami 解决方案如下&#xff1a; 如果这里网关为空&#xff0c;那么和我遇到的问题一样网关没有设置上&#xff0c;在这直接配置网关之后重启即可

【Spring Boot】如何自定义序列化以及反序列器

在我们使用默认的消息转换器&#xff0c;将java的Long类型通过json数据传输到前端JS时&#xff0c;会导致Long类型的精度丢失&#xff0c;这是因为JS处理Long类型数字只能精确到前16位&#xff0c;所以我们可以采用自定义序列化方式将Long类型数据统一转为String字符串&#xf…

windows10关闭自动更新

windows10关闭自动更新 下载策略组改配置组配置 下载策略组 我自己的电脑里没有这个文件gpedit.msc所以要下载 pushd "%~dp0"dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txtdir /b C:\Windows…

【算法基础】高精度运算

文章目录 高精度加法高精度减法高精度乘法高精度除法 高精度加法 主要思路就是利用编程模拟人工的加法 方便操作使用vector来存储这些数 为了方便进位&#xff0c;需要将原数字倒过来存储到vector中 加完所有位之后别忘了判断最后一位的进位 最后需要将vector中的数倒着输出 …

机器学习算法项目开发流程

机器学习算法是当今人工智能领域最重要的技术之一&#xff0c;它可以让计算机通过学习数据中的模式和规律来实现预测和决策。在实际应用中&#xff0c;开发一个成功的机器学习算法项目需要遵循一定的开发流程。本文将介绍一个常见的机器学习算法项目开发流程&#xff0c;帮助读…

odoo16前端框架源码阅读——env.js

env.js&#xff08;env的初始化以及服务的加载&#xff09; 路径&#xff1a;addons\web\static\src\env.js 这个文件的作用就是初始化env&#xff0c;主要是加载所有的服务。如orm, title, dialog等。 1、env.js 的加载时机 前文我们讲过前端的启动函数&#xff0c;start.…

系统安全测试详解

一、前言 我刚开始接触安全测试的时候&#xff0c;想的最多就说那种在昏暗的灯光下&#xff0c;带着神秘面具的黑客&#xff0c;对着键盘噼里啪啦一顿猛如虎的操作&#xff0c;然后长舒一口气&#xff0c;最后来了句yes&#xff0c;完美收工&#xff01; 随后的职业生涯中&am…

LaTex 空行

在LaTeX中&#xff0c;可以通过命令\vspace{\baselineskip}或者使用\bigskip来插入一个空行。 以下是示例代码&#xff1a; 这是一段文字。\vspace{\baselineskip}这是下一段文字。或者&#xff1a; 这是一段文字。\bigskip这是下一段文字。

你是否了解Spring @EventListener注解?

当创建一个大型的应用程序时&#xff0c;我们经常需要处理各种事件。Spring框架提供了一个强大的事件处理机制&#xff0c;允许我们在应用程序中定义和监听事件。其中&#xff0c;EventListener注解是Spring框架中用于监听事件的主要注解之一。在本篇博客中&#xff0c;我们将深…

Django学习日志08

如何开启事务 事务的目的&#xff1a;为了保证多个SQL语句执行成功&#xff0c;执行失败&#xff0c;前后保持一致&#xff0c;保证数据安全 ACID属性&#xff1a; A&#xff1a;原子性&#xff08;Atomicity&#xff09;&#xff1a;指事务是原子的&#xff0c;对事务中的操…

Mysql 索引与事务

1. 索引 1.1 什么是索引 当我们看一本书时可以通过目录快速的定位到我们想要的章节 &#xff0c;在数据库中查询数据也需要遍历表&#xff0c;而且数据库是把数据存储在硬盘上的&#xff0c;所以读取数据十分的慢&#xff0c;因此就可以给数据库引入索引&#xff0c;提高查询…

python数据可视化之matplotlib.pyplot

文章目录 模块引用折线条图实际应用案例关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 不论是数据挖掘还是数据…

Hadoop-- hdfs

1、HDFS中的三个进程&#xff1a;NameNode&#xff08;NN&#xff09;、DataNode(DN)、SecondNameNode(SNN) 2、NameNode&#xff08;NN&#xff09; 1、作用&#xff1a; 1、接收客户端的一个读、写的服务&#xff0c;在namenode上存储了数据文件和datanode的映射的关系。 …

移动云电脑:摆脱传统桎梏,助推企业数字化转型

如今&#xff0c;随着“云”在企业数字化转型战略中的作用日益凸显&#xff0c;上云是企业数字化转型第一步&#xff0c;已成为业界共识。尤其对于中小企业而言&#xff0c;数字化转型更是一种生存之道。 实际上&#xff0c;企业数字化转型面临很多传统桎梏。例如&#xff0c;数…

企业数字化转型的好处?_光点科技

企业数字化转型是当今商业世界中一个至关重要的议题。数字化转型不仅仅意味着采用新技术&#xff0c;而是涉及到企业在文化、运营和客户体验方面的根本变革。那么&#xff0c;企业数字化转型的好处是什么呢&#xff1f; 1.数字化转型可以显著提高企业的运营效率。 通过自动化流…

Hafnium之分区运行时模型

端点的运行时模型描述了执行上下文在不同状态之间允许的转换。下面是支持的四种分区运行时模型(请参阅[1]第7节): RTM_FFA_RUN:呈现给执行上下文的运行时模型,该执行上下文通过FFA_RUN接口分配CPU周期。RTM_FFA_DIR_REQ:呈现给执行上下文的运行时模型,该执行上下文通过FFA_…

java-jdbc快速入门

文章目录 简介快速入门 简介 JDBC就是使用Java语言操作关系数据库的一套APIJava DataBase Connectivity 快速入门 -- mysql 中准备工作 create database if not exists my_db; use my_db; create table account(id int,name varchar(20),money int ); insert into account v…