C++_多态

目录

1、什么是虚函数

1.1 什么是虚函数重写

1.2 虚函数的继承

1.3 协变 

1.4 析构函数的重写

2、override和final

2.1 final

2.2 override

3、纯虚函数/抽象类

3.1 接口继承和实现继承 

4、多态的原理


前言:

        在C++中,多态指的是调用同一个类的成员函数时,会产生两种不同的结果,因此多态又称多种形态。

实现多态的前提是:

        1、两个类必须满足继承关系。

        2、必须通过基类的指针或引用去调用该类的成员函数,并且这个成员函数是被virtual修饰过的虚函数,还得完成虚函数的重写

        多态结构示意图如下: 

1、什么是虚函数

        被关键字virtual修饰的类成员函数就是虚函数,比如下面的BuyTicket就是一个虚函数。

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

1.1 什么是虚函数重写

       在基类和派生类中都存在一个相同的虚函数(即函数名、函数形参类型及个数、返回类型都相同),这时候把派生类中的虚函数叫做基类虚函数的重写

        注意:虚函数重写和虚函数是两个概念,即虚函数不一定构成虚函数重写,构成虚函数重写的前提是两个函数必须是虚函数。

        体现虚函数重写的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#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;
}

        运行结果:

        从结果可以看到,虚函数重写的目的就是为了实现多态,上面代码通过同一个形参p调用两个不同的函数BuyTicket,从而达到实现不同的功能。

1.2 虚函数的继承

        在继承关系中,派生类可以继承基类的成员,那么虚函数也是可以被继承的,比如拿上述代码举例,Student类中如果没有对BuyTicket函数进行virtual修饰,却同样可以实现多态的效果,原因就是派生类继承了基类的虚函数virtual,让自己的BuyTicket也变成了一个虚函数。


         但是如果基类中的BuyTicket函数不是虚函数,而派生类的BuyTicket是虚函数则无非满足虚函数的重写。

1.3 协变 

        从上文可以得知,虚函数重写的要求是:函数名、函数形参类型及个数、返回类型都相同,则协变就是在此基础上放宽了对虚函数重写的要求,即返回类型不相同的虚函数也可以构成重写,但是返回的必须是指针或者引用,并且这两个虚函数的返回值是继承关系。

        协变示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person {
public:virtual Person& BuyTicket() { cout << "买票-全价" << endl; return *this; }
};
class Student : public Person {
public:virtual Student& BuyTicket() { cout << "买票-半价" << endl; return *this;}
};void Func(Person& p)
{ p.BuyTicket(); 
}
int main()
{Person ps;Student st;Func(ps);Func(st);return 0;
}

        运行结果:

        协变示意图如下:

1.4 析构函数的重写

        析构函数的重写可以不满足协变和虚函数重写的要求,只需要对两个类的析构函数加上virtual即可实现重写。原因在于派生类和基类的析构函数都会被统一处理成destructor的函数名,这时候他们就满足了虚函数重写的要求(此时函数名相同了)。

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person {
public:virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

        运行结果:

2、override和final

        override和final的目的就是手动的对虚函数重写进行严格的检查,因为在实现虚函数重写的时候有些疏忽编译器是不会报错的,因此我们可以使用override和final严格要求虚函数的重写。

2.1 final

        修饰一个虚函数,让其不能被重写。

        示例代码:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person
{
public:virtual void Number() final {}
};
class Student :public Person
{
public:virtual void Number() {}//由于基类在虚函数后面加了final,因此此处不能被重写
};int main()
{return 0;
}

         运行结果:

2.2 override

        override的作用是检查被修饰的虚函数是否重写了基类的某个虚函数。

        override示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person
{
public://virtual void Number()  {}
};
class Student :public Person
{
public://Person类中没有实现Number虚函数,因此此处没有实现虚函数的重写virtual void Number()override {}
};int main()
{return 0;
}

        报错原因:

3、纯虚函数/抽象类

        在虚函数的声明末尾处写上“=0”,则该虚函数为纯虚函数。而这个纯虚函数所在的类称之为抽象类,即不能用该类实例化对象,并且继承该抽象类的派生类也不能实例化出对象。

        纯虚函数/抽象类的写法:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person
{
public:virtual void Number()=0  {}
};
class Student :public Person
{
public://virtual void Number() {}
};int main()
{Person p;Student s;//Person和Student都不能实例化出对象return 0;
}

        只有重写了该抽象类中的纯虚函数,才能用派生类实例化出对象(基类还是不能实例化对象):

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person
{
public:virtual void Number()=0  {}
};
class Student :public Person
{
public:virtual void Number() {}//重写纯虚函数
};int main()
{//Person p;//Person依旧不能实例化对象Student s;//由于Student中的Number重写了Person的Number,因此Student可以实例化对象return 0;
}

3.1 接口继承和实现继承 

        实现继承就是我们所说的派生类继承了基类的成员函数,继承的是该函数的整体。而接口继承是虚函数继承,虚函数继承的只是函数的接口部分,并不是整个函数。他们是有区别的。

        体现他们区别的示意图如下:

4、多态的原理

        实现多态的根本原因是虚函数的重写,而在底层中,在基类部分会自动生成一个数组指针(又称虚表指针),该指针指向的数组是用来存放虚函数地址的(这个生成动作在编译阶段就已经完成了),所以该数组又称虚函数表

        因此当调用条件满足多态时,编译器实际上会到基类中找到虚表指针,然后调用的是该指针指向的虚函数,这也是为什么通过同一个接口可以实现两个不同结果。

        可以通过观察一个类的大小来发现虚指针的存在:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};int main()
{Person ps;cout << sizeof(ps) << endl;//ps的大小为4return 0;
}

        通过监视窗口也可以发现虚表指针的存在:


        如果是继承关系,则虚表指针存放在父类中的基类部分中,这也是为什么实现多态的条件之一是需要基类指针调用虚函数,因为虚表指针存在基类部分中,使用基类指针刚好可以与其对应。

结语: 

        以上就是关于多态的讲解, 多态的本质就是继承,并且需要满足虚函数重写和基类指针、引用调用,多态实际上只需要记住这两点即可,只不过在此基础上可以延申出更多的细节。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

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

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

相关文章

深度学习图像分类相关概念简析+个人举例3(CNN相关补充,附详细举例代码1)

【1】激活函数&#xff08;Activation Function&#xff09;&#xff1a;在深度学习&#xff08;CNN&#xff09;中&#xff0c;激活函数用于引入非线性性质&#xff0c;帮助模型学习复杂的关系。常见的激活函数有ReLU、Sigmoid和Tanh等。 &#xff08;1&#xff09;ReLU激活函…

【HTML】MDN

文章目录 一、html元素1.1 <a>1.2 <abbr>1.3 <address>1.4<area>1.5 <article>1.6 <aside>1.7 <audio>1.8 <b>1.9 <base>1.10<bdi>1.11 <bdo>1.12 <blockquote>1.13 <body>1.14 <br>1.15…

如何在Linux部署Yearning并结合cpolar实现公网访问内网管理界面

文章目录 前言1. Linux 部署Yearning2. 本地访问Yearning3. Linux 安装cpolar4. 配置Yearning公网访问地址5. 公网远程访问Yearning管理界面6. 固定Yearning公网地址 前言 Yearning 简单, 高效的MYSQL 审计平台 一款MYSQL SQL语句/查询审计工具&#xff0c;为DBA与开发人员使用…

ERR_SSL_VERSION_OR_CIPHER_MISMATCH

我在namesilo买的域名&#xff0c;coludflare做的解析&#xff0c;华为云的SSL&#xff0c;用宝塔部署的SSL&#xff0c;访问https报错&#xff0c;http却正常&#xff1a; 报错&#xff1a;此网站无法提供安全连接www.hongkong.ioyunxin.top 使用了不受支持的协议。 ERR_SSL_…

22.HarmonyOS App(JAVA)位置布局PositionLayout使用方法

不常用 在PositionLayout中&#xff0c;子组件通过指定准确的x/y坐标值在屏幕上显示。(0, 0)为左上角&#xff1b;当向下或向右移动时&#xff0c;坐标值变大&#xff1b;允许组件之间互相重叠 布局方式 PositionLayout以坐标的形式控制组件的显示位置&#xff0c;允许组件相…

0-MQTT基础使用教程【学习】

文件路径 MQTT基础使用教程1. MQTT1.1 MQTT简介1.1.1 什么是MQTT1.1.2 设计原则1.1.3 应用领域1.2 MQTT协议相关概念1.2.1 MQTT协议实现方式1.2.2 MQTT协议中的方法1.3 消息服务质量QoS1.3.1 消息服务质量QoS三个等级1.3.2 发布与订阅QoS1.4 Topic通配符匹配规则2. EMQX2.1 EMQ…

深入理解Java中的二叉树

目录 一、什么是二叉树? 二、二叉树的主要类型 三、二叉树的实现 四、二叉树的应用 五、关于二叉树的题目 引言: 二叉树是计算机科学中常用的一种数据结构&#xff0c;它是由节点组成的层级结构&#xff0c;每个节点最多有两个子节点。在Java编程语言中&#xff0c;二…

时间回显+选择(年月日时分秒

一、获取某个时间 1、Date获取Date类型 <el-form-item label"时间" name"endTime"><el-date-picker type"datetime" v-model"editForm.endTime"></el-date-picker> </el-form-item> 效果如图&#xff1a; …

设计模式_责任链模式_Chain

案例引入 学校OA系统的采购审批项目: 需求是 采购员采购教学器材如果金额 小于等于5000&#xff08;0<x<5000&#xff09;&#xff0c;由教学主任审批如果金额 小于等于10000&#xff08;5000<x<10000&#xff09;&#xff0c;由院长审批如果金额 小于等于30000&…

基于Springboot的考编论坛网站的设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的考编论坛网站的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

二、SSM 整合配置实战

本章概要 依赖整合和添加控制层配置编写(SpringMVC 整合)业务配置编写(AOP/TX 整合)持久层配置编写(MyBatis 整合)容器初始化配置类整合测试 2.1 依赖整合和添加 数据库准备 数据库脚本 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT…

投稿文件准备复盘

8月底 1、实验做完&#xff0c;数据产出后&#xff0c;开始分析数据&#xff0c;讨论&#xff0c;分析&#xff0c;讨论&#xff0c;补充实验 12月底 开始着手于投稿论文 2.1、上传数据到GEO&#xff0c;要到GEO号&#xff08;1周左右如果不打回的话&#xff09; 2.2、首先…