C++中的多态(上)

news2025/7/8 21:23:13

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
在这里插入图片描述

文章目录

  • 一、多态的概念
  • 二、虚函数
  • 三、破坏多态条件的现象
    • 1.破坏多态条件一,虚函数重写/覆盖
    • 2.破坏多态条件二
  • 四、 多态的两个条件
    • 不满足这两个条件就不构成多态的原因
  • 五、多态的两个特例
    • 特例一:子类对应成员函数可以不加virtual
    • 特例二:重写的协变
  • 六、多态的原理
  • 七、小总结
  • 八、析构函数的重写
    • 析构函数不定义成虚函数的问题

一、多态的概念

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

比如买票,普通人正常买票,学生半价买票,军人优先买票

二、虚函数

虚函数是多态的第一个条件
加了virtual就是虚函数,就会有虚表

class Person
{
public:
	virtual void BuyTicket(){cout<<"买票-全价"<<endl;}
	//虚函数只能用于成员函数
};

class Student:public Person
{
public:
	virtual void BuyTicket(){cout<<"买票-半价"<<endl;}
	//虚函数只能用于成员函数
};

class Soldier/*军人*/:public Person
{
public:
	virtual void BuyTicket(){cout<<"优先-买票"<<endl;}
	//虚函数只能用于成员函数
};

有了虚函数,如果构成多态,那么这里的函数就不是构成隐藏了,而是构成重写(虚函数重写/覆盖)
要求虚函数+三同:函数名,参数列表,返回值都要相同

如果不符合重写/覆盖,就是隐藏/重定义

虚函数和虚函数表,如果没有构成多态,那么没有价值,如果不是去实现多态而写出 虚函数,那么就是白白的浪费

三、破坏多态条件的现象

1.破坏多态条件一,虚函数重写/覆盖

需要破坏三同
也就是破坏返回类型,参数列表,函数名中任意一个
在这里插入图片描述
可以看出,sd对象的调用已不构成多态,原因就在于破坏了虚函数重写

2.破坏多态条件二

变成不是指针的引用或者指针去调用虚函数
在这里插入图片描述

四、 多态的两个条件

</font color=“red”>1.虚函数(不重写也是多态,只是不重写的话体现不出效果)
2.父类的指针或者引用去调用虚函数(不满足重写虚函数就当普通函数处理)

不满足这两个条件就不构成多态的原因

class A
{
public :
	void func(int val)
	{
		cout<<"A->"<<val<<endl;
	}
};
class B:public A
{
public :
	void func(int val)
	{
		cout<<"B->"<<val<<endl;
	}
};

1.如果不虚函数重写,那么父类的指针或者引用看到的就只是父类的成员函数,比如上面这个代码,如果不重写,那么不管是A的对象,引用还是指针,会发生切片/切割,那么都只能够看到A->这个结果。(重写的原理,下面讲)

2.如果不用父类,而用子类,那么只构成了隐藏/重定义,所以要么显示的去调用父类的func,看到A->的结果,要么只能调用自己的func,看到B->的结果

3.不能够使用父类的对象来完成多态,不能完成多态的原因是没有构成多态,是普通调用,但是就算是多态调用,虚表中也不对(因为同类对象用的是同一张虚表,并不是简单的切分或者切片)

五、多态的两个特例

特例一:子类对应成员函数可以不加virtual

子类的对应成员函数可以不加关键字virtual,因为是先把父类继承下来,最好加上
这是因为虚表会继承给子类,编译器会默认认为你这个函数是需要重写的,因为你父类写虚函数就是为了完成多态,不然没有意义,所以直接就默认重写。(其实真正原因是虚函数是接口继承,下一篇会讲到)

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

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

特例二:重写的协变

重写的协变是针对三同中的返回类型来说的,要求是返回类型可以不同,但是必须是有父子关系的指针或者引用

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

class Student:public Person
{
public:
	B*/B& BuyTicket(){cout<<"买票-半价"<<endl;}
};

上面代码中的A是B的基类,这两个的顺序不能够交换

六、多态的原理

class Base
{
public:
	virtual void Func1()
	{
		cout<<"Func1()"<<endl;
	}
private:
	int _b=1;
	char _ch = 'A';
};
//sizeof(Base)的结果是12

其实,还存了一个虚函数表指针_vfptr(virtual function pointer),指向虚函数表
vftable,这是一个函数指针数组,虚函数放进虚函数表,和继承当中的虚基表有所不同,虚函数表存的是函数地址,而虚基表存的是基类对象的存储位置与当前位置的偏移量
在这里插入图片描述
那么继承的时候,会继承父类的虚函数表下来
在这里插入图片描述
可以看出,虚基表是复制了一张,然后如果没有重写的话,用的就是同一个函数(基类的成员函数)
在这里插入图片描述
当我重写了之后,会把虚函数表中存储的函数地址改成重写之后的函数。
然后父类的指针或者引用去调用的时候,找到的就是虚表/虚函数表中重写的那个函数,也就达成了多态

</font color=“red”>对于为什么多态的调用不能用父类对象,而是用父类的指针或者引用的原因如下
在这里插入图片描述
(仔细看Base b2 =d ,可以看出b2并不是通过d来直接切分/切片,而是同一类型的对象使用同一张虚表 。因为这样的机制,所以父类对象不能用来完成多态。)但是真正原因是没有构成多态,是普通调用,发生切分/切片

七、小总结

1、多态的本质原理就是:不构成多态,则调用自己应该调用的函数,构成多态,则去调用虚表中相应函数。
在这里插入图片描述
不构成多态:
如果这里用子类的对象/指针/引用去调用,因为隐藏/重定义,调用结果func2
如果用父类的对象/指针/引用,不构成多态,会去直接调用父类的函数func1
在这里插入图片描述
构成多态:
如果这里用子类的对象/指针/引用去调用,因为隐藏/重定义,调用结果func2
如果这里用父类的指针/引用,因为构成多态,找虚表,因为重写,调用结果func2
如果这里用父类的对象,所以调用func1(这里调用func1不是因为同类对象是同一张虚表,而是因为不构成多态,直接调用的是父类的func1)

2.普通调用是编译时决议,在编译或链接时就已经确定了调用的函数的地址(编译时有定义就直接拿到,而链接的时候就再去找没拿到地址的函数,比如多个源文件的链接,或者说链接阶段去共享区找动态库)

3.多态调用是运行时决议,需要在程序运行时去指向对象的虚表中找到函数的地址,进行调用(构成多态就是多态调用)

八、析构函数的重写

class Person
{
public:
	virtual ~Person()
	{
		cout<<~Person()<<endl;
	}
};
class Student:public Person
{
public:
	virtual ~Student()
	{
		cout<<~Student()<<endl;
	}
};

</font color=“red”>为了多态的析构重写,编译器把析构函数名同一成了destructor,所以能完成重写(在继承时就已经说到了这个,所以析构会构成隐藏,父类的析构会在子类的析构后自动调用)

所以建议在继承中析构直接定义成虚函数

析构函数不定义成虚函数的问题

Person* p = new Student();

如果此时析构函数不是虚函数,那么最后释放的时候我们会delete p,因为不构成多态,这只是一个普通调用(编译时决议),所以因为切片,调用的只是父类的析构,如果不把析构定义成虚函数,那么永远就只能调用父类的析构,内存泄漏

</font color=“red”>因为子类的析构调用完后会自动调用父类的析构(继承中讲过,是为了保证他的析构顺序),所以重写之后调用子类的析构destructor资源就能全部释放
在这里插入图片描述

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

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

相关文章

数据库审核工具SQLE部署及使用

点击上方蓝字关注我SQLE&#xff08; https://opensource.actionsky.com/sqle/ &#xff09;是由上海爱可生信息技术股份有限公司 开发并开源&#xff0c;支持多场景审核&#xff0c;支持标准化上线流程&#xff0c;原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。我们…

PC_磁盘HDD_SSD

文章目录磁盘存储器组成磁盘驱动器磁盘控制器盘片platter存储区域磁盘结构磁道track扇区sector&#x1f388;/块Block&#x1f386;磁头(Head)圆柱面cylinder磁记录原理磁盘性能指标记录密度磁盘的容量非格式化容量格式化容量数据传输率磁盘转速旋转周期T例平均存取时间纯读/写…

网络与通信程序设计-基于UDP的广播通信实例

目录 实验内容和设计思想 实验的内容 UDP的设计思想 UDP的协议头部 UDP通信编程思想 UDP的工作流程 UDP编程收发函数 广播通信 广播模式设置 广播套接字 UDP Socket的使用过程 UDP广播通信实例实现 initsock.h 服务器发送广播消息 客户端接收广播消息 运行效果 …

HttpMessageConverter 消息转换器

HttpMessageConverter 简介 HttpMessageConverter 是SpringMVC中提供的一个策略接口&#xff0c;它是一个消息转换器类&#xff0c;Spring Mvc中就是由HttpMessageConverter负责转换HTTP的请求和响应。 默认情况下&#xff0c;Spring Boot 会自动加载如下消息类型转换器&…

spring复习01,IOC的思想和第一个spring程序helloWorld

spring复习01&#xff0c;IOC的思想及本质IOC的思想一个小例子&#xff0c;来体会IOC的基础思想。dao层dao层接口dao层实现类service层service层接口service层实现类测试代码service层实现类修改测试代码修改IOC容器在spring中的简单实现创建项目在pom.xml中引入依赖实体类创建…

C# 拨号面板 高亮显示

一 拨号面板 显示&#xff1a;绘制4x3; 数据&#xff1a;每一格对应一个数字&#xff1b; 行为&#xff1a;点击单元格时有反应&#xff1b; 二 拨号事件 当点击某个号码时&#xff0c;触发拨号事件。 设计思路&#xff1a; ① 重写OnMouseClick(); ② 根据鼠标点击位置&am…

JVET-AB0117-基于模板的帧内推导的方向性融合

本提案是针对 ECM 中的 TIMD&#xff08;基于模板的帧内模式推导&#xff0c;Template based intra mode derivation &#xff09; 的技术的加权方式的改进。具体地&#xff0c;本提案提出使用方向混合&#xff08;directional blending&#xff09;来加权TIMD使用模板推导的两…

SpringBoot SpringBoot 原理篇 1 自动配置 1.13 bean 依赖属性配置

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇1 自动配置1.13 bean 依赖属性配置1.13.1 环境准备1.13.2 bean 依赖属性配置1…

【软件测试】作为测试人,因工作与开发吵了一架碰撞,该咋办......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 测试与开发在工作中…

【网页设计】HTML+CSS保护野生动物北极熊介绍网页设计专题

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

傻白入门芯片设计,RDL/Interposer/EMIB/TSV(五)

一、再分配层&#xff08;RDL&#xff09; 主要原理就是在晶片表面沉积金属层和介电层。它形成了一个再分配层&#xff0c;以携带相应的金属布线模式&#xff0c;并在芯片外的松散区域上重新排列芯片的IO端口。由于RDL形成的金属布线的线宽和间距较小&#xff0c;从而提供了更…

MySQL之InnoDB架构浅析

InnoDB是一个兼顾高可靠性和高性能的通用存储引擎。在 MySQL 5.7 中&#xff0c;InnoDB是默认的 MySQL 存储引擎。 InnoDB 的主要优势 支持事物&#xff0c;具备crash-safe的能力支持行锁以及MVCC&#xff0c;具备良好的多用户并发性和性能Buffer Pool&#xff0c;提升热点数…

第8章 综合案例—构建DVD租赁商店数据仓库

目录 章节概要 案例背景介绍 数据仓库的架构模型 数据仓库的架构模型 数据库sakila的下载和安装 数据库sakila简介 数据库sakila中 数据表之间的关系 数据表简介 用于储存电影基本信息及相关介绍的数据&#xff0c;该数据表各个字段的含义如表。 用于储存定义电影id所…

Python爬虫

前言 这个故事&#xff0c;从头讲起。 2022 年上班第一天&#xff0c;我们做了一个重要决定&#xff0c;就是打造精品学习路线&#xff0c;为初学者指明前进的方向&#xff0c;以及常见的避坑技巧。 &#xff08;文末送读者福利&#xff09; 我承认这是一件非常有挑战的事情…

自动驾驶感知算法实战6——目标分类详解(ResNet、VGG、GoogLeNet等)

自动驾驶感知算法实战专栏:https://blog.csdn.net/charmve/category_12097938.html目录 1 网络分类2 详解 ResNet3 详解 VGG4 稠密连接网络(DenseNet)5 详解 GoogLeNet6 详解 Fast R-CNN1 网络分类 来自:https://charmve.github.io/computer-vision-in-action/#/book_prefa…

构建房地产行业智慧采购新模式,采购协同商城系统护航企业采购数字化转型

采购是房地产企业控制成本的龙头&#xff0c;同时还直接影响着工程质量。随着采购工作推进的不断深化&#xff0c;对地产物资采购精细化管理的要求也在日益加强&#xff0c;如何做好采购工作成为房地产企业实现数字化转型升级进程必须突破的壁垒。 房地产作为一个资源整合性行…

排序算法及java实现

简介 排序算是非常基础的算法&#xff0c;为什么我们需要排序算法呢&#xff1f;假设我们只有10个数&#xff0c;或者100个数&#xff0c;其实根本不需要研究这么多的排序算法&#xff0c;正常我们会使用的插入排序或者选择排序足够了&#xff0c;没必要发明快排&#xff0c;基…

第九章 哈希表 AcWing 2 1549. 集合相似度

第九章 哈希表 AcWing 2 1549. 集合相似度 原题链接 AcWing 1549. 集合相似度 算法标签 哈希表 位运算 思路 使用unordered_set数据结构将数据插入指定集合 如何计算Nc&#xff08;两集合交集&#xff09; 如何计算Nt&#xff08;两集合并集&#xff09; 输出问题 WA 精…

干掉 “重复代码”,这三种方式绝了!

软件工程师和码农最大的区别就是平时写代码时习惯问题&#xff0c;码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。 业务同学抱怨业务开发没有技术含量&#xff0c;用不到设计模式、Java 高级特性、OOP&#xff0c;平时写代码都在堆 CRUD&#xff0c;个…

BCD编码和ASCII码

计算机里&#xff0c;字母&#xff0c;各种字符以及指挥计算机执行操作的指令&#xff0c;均用二进制数的组合表示&#xff0c;称为二进制编码 目录 ASCII码&#xff08;汇编语言程序设计的时候会常用&#xff09; BCD码&#xff08;了解&#xff09; ASCII码&#xff08;汇…