【C++】详解多态

目录

初识多态

多态的条件

接口继承和实现继承

override 和 final

多态原理

继承与虚函数表

析构函数与多态

抽象类


本篇内容关联知识的链接

【C++】详解C++的继承-CSDN博客

【C++】详解C++的模板-CSDN博客

【C++】C++的内存管理-CSDN博客

初识多态

父类被不同子类继承后,父类呈现出不同的状态

假设有人这个类,这个类的状态是工作。当人被学生类继承后,人这个类的状态就变成了学习。当人被程序员类继承后,人这个类的状态就变成了写代码。当人被厨师类继承后,人这个类的状态就变成了做饭

多态的条件

 必须通过父类的指针或者引用调用虚函数
 被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写
虚函数
virtual 修饰的类 成员函数 称为虚函数
class Person {
public:virtual void BuyTicket() { cout << "工作" << endl;}
};
虚函数重写
子类中有一个跟父类完全相同的虚函数 ( 即子类虚函数与父类虚函数的 返回值类型、函数名字、参数列表完全相同 ) ,称子类的虚函数重写了父类的虚函数。
. 协变 ( 基类与派生类虚函数返回值类型不同 )
派生类重写父类虚函数时,与父类虚函数 返回值类型 不同。即父类虚函数返回父类对象的指
针或者引用,子类虚函数返回子类对象的指针或者引用时,称为协变。
如下是协变示例代码
class A{};
class B : public A {};
class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};
重写是用子类函数的实现把父类函数的实现覆盖掉
完成重写后,再去调用子类中父类的虚函数时,不再是父类原来的虚函数
如下是多态代码示例
#include<iostream>
using namespace std;class Person {
public:virtual void BuyTicket() { cout << "工作" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "学习" << endl; }
};class programmer : public Person { 
public:virtual void BuyTicket() { cout << "写代码" << endl; }
};class cook : public Person { 
public:virtual void BuyTicket() { cout << "做饭" << endl; }
};void Func(Person& p) //父类的指针或引用{p.BuyTicket();
}
int main()
{Person ps;Student st;programmer pr;cook ck;Func(ps);Func(st); Func(pr);Func(ck); return 0;
}

接口继承和实现继承

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

override 和 final

final
修饰虚函数,表示该虚函数不能再被重写
如下是代码示例
class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout << "Benz-舒适" << endl;}
};
override :
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
如下是代码示例
class Car{
public:virtual void Drive(){}
};
class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

多态原理

类对象的顶部存有一个指针,该指针指向虚函数表,虚函数表中存放虚函数的指针。

既然该指针存放在对象的顶部,就会有个很容易混淆的问题:

如果有个指针指向对象,那么不同类型的指针会有不同的含义。对象指针:指向该对象。二级指针:指向——>指向虚函数表的指针

虚函数表是一个函数指针数组,虚函数表的最后一般以空结尾,和字符串以\0结尾类似,都是为了控制结束条件。

虚函数表中只存虚函数的函数指针

那么便容易理解为什么要调用父类对象的指针或引用了

从语言层理解:多态是父类在不同的子类中呈现不同状态,既然如此,用父类的指针或引用调用一样的函数,却能呈现不同的结果,才能对比出多态的意义。

从底层理解:指向虚函数表的指针是存给父类的。因为在子类的内存模型中,父类的数据在上,子类的数据在下。

用父类类型指针去调用子类中父类虚函数发生了切片

理解多态原理:

因为每个父类中都有一张自己的虚函数表(有虚函数才有虚函数表,要形成多态的的话父类一定会有虚函数),而子类中父类的虚函数表中的虚函数被子类重写了。

即使用相同类型的指针或引用去调用,即使被调用函数的函数名,参数,返回值也相同。

但重写之后,函数的具体实现不同了,这就实现了多态。

继承与虚函数表

不同的继承 虚函数表是怎么存虚函数指针呢

单继承

代码示例如下

class Base { 
public :virtual void func1() { cout<<"Base::func1" <<endl;}virtual void func2() {cout<<"Base::func2" <<endl;}
private :int a;
};
class Derive :public Base { 
public :virtual void func1() {cout<<"Derive::func1" <<endl;}virtual void func3() {cout<<"Derive::func3" <<endl;}virtual void func4() {cout<<"Derive::func4" <<endl;}
private :int b;
};

模型如下

多继承

代码示例

class Base1 {
public:virtual void func1() {cout << "Base1::func1" << endl;}virtual void func2() {cout << "Base1::func2" << endl;}
private:int b1;
};
class Base2 {
public:virtual void func1() {cout << "Base2::func1" << endl;}virtual void func2() {cout << "Base2::func2" << endl;}
private:int b2;
};
class Derive : public Base1, public Base2 {
public:virtual void func1() {cout << "Derive::func1" << endl;}virtual void func3() {cout << "Derive::func3" << endl;}
private:int d1;
};

模型如下

子类中有多个父类都有虚函数,那么会建多张虚函数表,第一个父类建的虚函数表是第一张虚函数表。

如果是多继承,子类自己的虚函数会存于第一张虚函数表中

上述模型中,父类1和父类2的func1都被子类重写,但第一张虚函数表中存的才是func1的函数指针。第二张表中存的是经过封装后的函数指针,该指针最终会去第一张虚函数表中找func1的函数指针,而func2没有被子类重写,两张表中存的是各自的函数指针,两个func2也没有关系,因为在不同的类域中(了解即可)

析构函数与多态

父类的析构函数只要加上关键字virtual就能和子类构成多态关系,虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处 理,编译后析构函数的名称统一处理成destructor

父类的析构为什么要和子类的析构构成重载关系呢

请看如下场景

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

那么上述场景中,资源会被清理干净吗

显然不会,因为只调用了两次Person类的析构,并没有调用Student类的析构。为什么呢

这是因为p1,p2的指针类型是父类的,delete只会调用父类的析构函数。

这时应该让Student类的析构与Person类的析构构成多态关系,让Student类的析构函数重写Person类的析构函数。

此时delete再通过父类类型的指针调用父类的析构时,实际上调用的是子类的析构函数,子类的析构函数。子类的析构函数在清理子类数据时,也会调用父类的析构函数清理父类数据。这样就完成了资源的清理。

如下图

抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。子类继承后也不能实例化出对象,只有重写纯虚函数,子类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
代码示例如下
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

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

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

相关文章

报名开启!2024 开源之夏丨Serverless Devs 课题已上线!

Serverless 是近年来云计算领域热门话题&#xff0c;凭借极致弹性、按量付费、降本提效等众多优势受到很多人的追捧&#xff0c;各云厂商也在不断地布局 Serverless 领域。 Serverless Devs 是一个由阿里巴巴发起的 Serverless 领域的开源项目&#xff0c;其目的是要和开发者们…

leetcode以及牛客网单链表相关的题、移除链表元素、链表的中间节点、合并两个有序链表、反转链表、链表分割、倒数第k个节点等的介绍

文章目录 前言一、移除链表元素二、链表的中间节点三、合并两个有序链表四、反转链表五、链表分割六、倒数第k个节点总结 前言 leetcode以及牛客网单链表相关的题、移除链表元素、链表的中间节点、合并两个有序链表、反转链表、链表分割、倒数第k个节点等的介绍 一、移除链表元…

扫盲:如何提升医学图像分割性能-to do list

导读&#xff1a;本文主要讨论了如何改进图像分割项目中的分割性能&#xff0c;包括一般性和具体性的问题解决方案&#xff0c;以及如何通过调整模型参数、改善数据集质量、优化模型架构、调整超参数、增加训练时长、改善图像分辨率和后处理技术等方法来提高分割效果。 图像分…

拼多多暂时超越阿里成为电商第一

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 拼多多的财报又炸裂了&#xff1a; 拼多多发布了第一季度财报&#xff0c;营收868亿&#xff0c;增长了131%&#xff0c;净利润279亿&#xff0c;增长了246%&#xff0c;营销服务收入424亿&#xff0c;也就是商家的…

小林coding笔记

MySQL执行流程 MySQL 的架构共分为两层&#xff1a;Server 层和存储引擎层。Server 层负责建立连接、分析和执行 SQL。存储引擎层负责数据的存储和提取。 Mysql执行 启动Mysql net start mysql登陆 mysql -u root -p输入密码

SwiftUI中的动画.animation和withAnimation

动画是通过改变视图的状态来给视图添加平滑视图变化的能力。SwiftUI中有两种类型的动画:隐式动画和显式动画。 不管是哪种动画&#xff0c;我们都需要一个被State包装的状态属性值&#xff0c;通过这个值的改变来促使与之相关的UI刷新&#xff0c;继而执行动画。 隐式动画.ani…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-19讲 串口实验UART

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

Vivado IP核的快速入门 官方手册和例程

在IP Catalog中选择要使用的IP核&#xff0c;可以查看支持的器件与资料。 在设计源sources页面中选中配置完成的IP核点击右键选择 Open IP Example Design&#xff0c;等待工程加载完成即可&#xff0c;可以点击Run Simulation进行功能仿真进行IP核的学习。 参考&#xff1…

Mac Pro中的开源虚拟机UTM安装ubuntu(Applce M1,M2芯片)(1)

MacPro安装UTM 1 UTM 下载UTM虚拟机链接: https://mac.getutm.app/ 建议官网下载&#xff1a; 下载 Ubuntu Arm 64版 下载 Ubuntu Arm 64版链接: https://cn.ubuntu.com/download/server/arm 2 安装UTM 2.1 安装在mac上 2.2 点Open 2.3 建虚拟机### 2.4 点出虚拟机 2.5 O…

前端性能优化:从系统分析讲到实践策略

前言 在过去几年&#xff0c;我曾经写过几篇和性能优化相关的文章&#xff0c;例如有性能优化方法相关的&#xff0c;有性能监控相关的。但是都只关注于局部&#xff0c;没有从整体上去看待、分析性能优化。所以本文打算尝试从整体上去分析前端性能优化&#xff0c;从性能指标…

使用 RisingWave 和 Redash 处理和可视化实时数据

在创建流处理管道时&#xff0c;需要两个关键组件&#xff1a;一个用于处理和转换数据&#xff0c;一个用于数据可视化。RisingWave 和 Redash 就提供了一个优秀的解决方案。 RisingWave 是一个支持实时数据处理的分布式 SQL 流数据库。它提供增量更新的物化视图&#xff0c;使…

TRICONEX 3720 技术特点

TRICONEX 3720是一款安全管理系统通讯模块&#xff0c;通常用于工业自动化和安全系统中。它的主要作用是在控制系统中处理和管理数据通讯&#xff0c;确保信息在各个组件之间的有效传递。以下是关于TRICONEX 3720的详细介绍&#xff1a; 功能定位&#xff1a;TRICONEX 3720作为…

计算机操作系统总结(1)

1操作系统的概念&#xff08;定义&#xff09;功能和目标 (1)什么是操作系统&#xff1f; &#xff08;2&#xff09;操作系统的功能和目标—作为系统资源的管理者 &#xff08;3&#xff09;操作系统的功能和目标—向上层提供方便易用的服务 &#xff08;4&#xff09;操作系…

IP学习——ospf1

OSPF:开放式最短路径优先协议 无类别IGP协议&#xff1a;链路状态型。基于 LSA收敛&#xff0c;故更新量较大&#xff0c;为在中大型网络正常工作&#xff0c;需要进行结构化的部署---区域划分、ip地址规划 支持等开销负载均衡 组播更新 ---224.0.0.5 224.0.0.6 …

区块链开发:区块链软件开发包装相关解析

区块链开发是指设计、构建和维护基于区块链技术的应用程序或系统的过程。区块链是一种分布式账本技术&#xff0c;它通过去中心化的方式记录和验证数据&#xff0c;确保数据的透明性、不可篡改性和安全性。区块链开发者使用各种编程语言和框架来创建这些应用程序。 在加密货币领…

【Linux】-Linux文件的上传和下载、压缩和解压[9]

目录 前言 一、上传和下载 1、使用finalshell对Linux系统进行上传下载 2、rz、sz命令 二、解压和压缩 1、压缩格式 2、tar命令压缩 3、tar命令压缩 4、zip命令压缩文件 5、unzip命令解压文件 前言 在Linux系统中&#xff0c;文件的上传和下载、压缩和解压是非常重要…

暴风雨的短视频:成都鼎茂宏升文化传媒公司

暴风雨的短视频&#xff1a;大自然的力与美 ​随着科技的进步和网络的普及&#xff0c;短视频已经成为我们生活中不可或缺的一部分。在这些短暂而精彩的瞬间里&#xff0c;我们得以窥见世界的每一个角落&#xff0c;感受生活的多样性和复杂性。成都鼎茂宏升文化传媒公司而当我…

go语言之基本数据类型

文章目录 基础数据类型分类整数类型有符号整数无符号整数默认整数类型 浮点类型复数类型布尔类型字符类型字符串replace字符串获取字符串长度字符串的拼接字符串获取指定位置字符更多string操作 数据类型之间的转换其它基本类型转字符串类型fmt包中的Sprintfstrconv包中函数 字…

java 解决异常 Class path contains multiple SLF4J bindings

前面 为了使用 j2cache pom中导入了 <dependency><groupId>net.oschina.j2cache</groupId><artifactId>j2cache-core</artifactId><version>2.8.4-release</version> </dependency><dependency><groupId>net.osc…

Java开发之JDBC

JDBC 介绍JDBC程序&#xff08;Statement&#xff09;相关细节URLResultSet 连接池程序&#xff08;PreparedStatement&#xff09; 本文主要记录一下学习JDBC的一些知识点 介绍JDBC 首先谈谈什么是JDBC。下面放几张图&#xff0c;大致就可以清楚JDBC了。程序&#xff08;Sta…