C++学习之多态详解

news2024/12/24 9:18:03

目录

多态的实现

例题

重载 重写  重定义的区别

抽象类

多态实现原理


多态的实现

C++中的多态是指,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。在C++中,通过将基类中的成员函数声明为虚函数,即可实现多态。

多态的发生是在继承的前提条件上,且要满足两个重要条件,否则都不能是多态:

1.虚函数的重写(要求三同,同函数名,同返回类型,同参数)-协变除外

2.父类的指针或者引用去调用函数

这与我们普通调用函数时所观察的函数类型不一样,多态调用看的是调用指针或者引用指向的对象,指向父类调用父类函数,指向子类调用子类的函数,这里他看的是指针或者引用指向的对象。

其次虚函数的重写存在两个例外:

1. 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
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;}
};
2. 析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor。
class Person {
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;//调父类析构
	//此时子类析构调用虚析构且这里的析构函数是一种重写
	delete p2;//先调子类析构释放子类自己的那一部分空间,再调父类析构,释放剩下的父类的空间
	return 0;
}

其次我们要知道,只有对于 new子类对象给父类指针时,才会需要调用虚析构,去释放除了子类本身的那一部分空间,还要释放继承父类的那一部分空间,否则会造成内存泄漏。

例题

下面用几道题检验我们的水平:

这里正确答案选择c项,对于p1它是父类指针B1指向子类对象,由于切片的原因,所以p1就是表示Base1的空间同理p2指向Base2的空间。但是p3只想自己,也就是它包含了继承的父类的空间,按照声明的顺序,p3指向Base1+Base2+Derive,首地址的话就是Base1,故选择c。

该题正确答案是B,相信大家可能都会选择D项,首先我们知道p指针是一个子类指针,但他继承了父类的成员函数,所以调用test是父类的函数,test再次调用func函数(这里的调用还是父类this调用func函数,继承父类的),由于指向的对象是子类对象,且满足函数重写,故这里会去调用子类的func,但是记住一点,子类的函数只会重写函数体,对于参数和函数名函数类型都是继承父类的,所以这里的缺省参数应该还是父类里的。

重载 重写  重定义的区别

总的来说对于继承不是重写就是重定义,函数重载参数不同(参数类型,参数个数,参数类型顺序)。

抽象类

当一个基类的成员函数不仅仅添加了virtual,并且函数体为空,如:Drive()这个函数

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

void Test()
{
Car* pBenz = new Benz;
 pBenz->Drive();
}

这样的函数我们将它称为纯虚函数,这样的类我们叫做抽象类。抽象类不能直接实例化对象

其次关键字overried可以检查虚函数是否完成重写。

多态实现原理

那么虚函数这种是怎么实现的呢?

在此之前我们先了解一下虚函数表:

首先对于虚函数的实现,在类中会有一个虚函数表指针,我们也可以根据类的大小看到有一个指针。

那么这个虚函数表指针是干嘛用的呢?

实际上一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
虚函数表本质上是一个指针数组,存放虚函数的指针。
父类是这样,那对于继承它的子类呢?我们用如下代码观察:
class Person {
public:
	virtual void Bytiket()
	{ 
		cout << "成人买票全价" << endl; 
	}
	virtual void Job()
	{ 
		cout << "我是医生" << endl;
	}
	void habit()
	{
		cout << "打球" << endl;
	}

private:
	int _b;
};
class Student : public Person {
public:
	virtual void Bytiket() { cout << "学生买票半价" << endl; }
private:
	int _d;
};
void Func(Person &p)
{
	p.Bytiket();
 }
int main()
{
	Person p;
	Student s;
	Func(p);
	Func(s);
	return 0;
}

首先对于父类,监视窗口并不能看到真正的情况,我们利用内存窗口再进行观察:

&p

 再详细的观察vfptr里面的内容

从内从中我们看到了确实和我们理解的一样,就是指针数组,里面存放着函数指针。

 此时我们再观察派生类里面的内容:

&s

vfptr

 可以看到子类中也有一个虚表指针,而且这与父类的虚函数表指针不一样,可以看到两者的虚函数指针地址都不一样,但是仔细观察里面存放的各个虚函数,可以看到第一个虚函数指针与父类的不一样,而第二个虚函数指针与父类的一样。

仔细一想我们大概就知道原因了,我们知道虚函数的重写其实是虚函数的覆盖,子类将虚函数表拷贝过来,在我们重写了某一个虚函数时,对应的虚函数指针就会被覆盖成新的,当我们不重写时,对应的虚函数地址没有发生改变,因此虚函数的重写本质上就是虚函数指针的覆盖。

完成覆盖后,当我们利用父类的指针或者引用指向子类对象,在调用时,就会调用完成覆盖后的虚函数的地址(新的虚表),此时调用的就是子类中重写的方法,这也就是我们会说调用的函数和指向的对象有关,指向子类调用子类的,指向父类调用父类的。本质就是指向某个对象的虚函数表。

那么又有一个问题我们也可以仔细想象了:为什么必须是父类对象的指针或者引用,对象就不行呢?

 了解到虚函数表的存在,我们再次思考,对象的指针或者引用那就是代表父类的这一部分的空间,指向子类对象时,中间不产生临时对象,可以当作切片剩下父类的那一部分,引用也就是直接引用那一部分。所以父类的引用与指针相当于就是子类中父类的那一部分空间,而我们用的是父类对象的话,父类对象指向子类对象,单单就是把子类中那父类的一部分给给父类但是不包括虚函数表,没有虚函数表多态就无法实现,故此必须是父类的指针或引用指向子类对象。

至于这里虚函数表不能拷贝,在设计之时就已经必须这样规定,如果虚函数指针也能被拷贝,那就全乱了,在调用时,该访问哪一个虚表?

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

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

相关文章

Spring IOC之@ComponentScan

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

[SWPUCTF 2023 秋季新生赛] web题解

文章目录 colorful_snakeNSS_HTTP_CHEKER一键连接!ez_talkPingpingpingUnS3rialize查查needRCE-PLUSbackup colorful_snake 打开题目&#xff0c;查看js源码 直接搜flag 把那三行代码复制到控制器&#xff0c;得到flag NSS_HTTP_CHEKER 都是http请求基本知识 抓包按照要求来&…

企业知识库管理系统怎么做?

21世纪&#xff0c;一个全新的信息化时代&#xff0c;从最初的传统办公到现在的信息化办公&#xff0c;一个世纪的跨越造就了各种大数据的诞生。 知识库系统 在这个数据横行的时代&#xff0c;文档管理产品市场逐渐兴盛起来&#xff0c;企业知识库管理系统作为企业的智慧信息的…

计算机组成原理new15 磁盘

文章目录 磁盘的结构磁盘的性能指标磁盘阵列固态硬盘SSD关于机械硬盘和固态硬盘的地址 这里计算传输时间和数据传输率是难点 磁盘的结构 注&#xff1a;磁盘的基本读取单位是扇区&#xff0c;但是每次只能读取1bit&#xff0c;这里我们应该理解为磁盘每次的读写操作至少要持续…

初始 c++(1)

目录: 目录 1: 命名空间的详解 2:c的输入与输出关键字及理解 3:详细讲解第一个c程序(每段代码的意思) 4:缺省参数的理解 5:函数重载 引言&#xff1a;从今天开始我们就开始学习c了&#xff0c;让我们一起开始新的知识吧&#xff01; 1&#xff1a;命名空间 所谓的命名空间…

深度学习学习笔记-模型的修改和CRUD

目录 1.打印模型,理解模型结构2.模型保存与加载3.模型的模块CRUD和模块的层的CRUD 1.打印模型,理解模型结构 import torchclass MyModel(torch.nn.Module):def __init__(self):super().__init__()self.layer1 torch.nn.Sequential(torch.nn.Linear(3, 4),torch.nn.Linear(4, …

云计算——网络虚拟化简介

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 前期回顾 前言 一.网络虚拟化 二.网络虚拟化介绍 三.为什么要网络虚拟化 四,网络…

STM32+2.9inch微雪墨水屏(电子纸)实现显示

本篇文章从硬件原理以及嵌入式编程等角度完整的介绍了墨水屏驱动过程&#xff0c;本例涉及的墨水屏为2.9inch e-Paper V2,它采用的是“微胶囊电泳显示”技术进行图像显示&#xff0c;其基本原理是悬浮在液体中的带电纳米粒子受到电场作用而产生迁移&#xff0c;从而改变显示屏各…

【C++初阶】小白入门C++

目录 前言&#xff1a;1、C关键字2、命名空间2.1命名空间是什么2.2为什么要有命名空间2.3命名空间怎么使用2.3.1命名空间的写法2.3.2命名空间是可以嵌套的2.3.3使用命名空间的三种方式 3、C输入和输出3.1初识cout和cin3.2C的输入输出可以自动识别变量类型 4、缺省参数4.1缺省参…

Linux:用户和权限

Linux&#xff1a;用户和权限 1. 认知root用户1.1 root用户&#xff08;超级管理员&#xff09;1.2 su和exit命令1.3 sudo命令1.3.1 为普通用户配置sudo认证 2. 用户、用户组管理2.1 用户组管理2.2 用户管理2.3 getent命令 3. 查看权限控制3.1 认知权限信息3.1.1 案例 4. 修改权…

react 实战- 玩转 react 全家桶(进阶)学习

一个命令是怎么运行起来的? Shell运行一个命令,实际上是运行一个脚本 环境变量 装了node以后,node的路径,就被注册到了环境变量里. 一个js的东西,可以注册? bin Webpack配置 构建 import A from A , const Arequire(A) 为什么可以这么写?为哈都行?本质上,是构建工…

人人自媒体的时候,Ai绘画还值得踏入吗?

前言 先说结论&#xff0c;如果你不打算涉足自媒体&#xff0c;平时也从不上网发什么内容去展示自己的话&#xff0c;其实AI绘画对你来说意义不大。但如果你对自媒体感兴趣&#xff0c;会涉及发作品&#xff0c;发内容&#xff0c;甚至去设计图片&#xff0c;那么AI绘画值得你…

【【萌新的SOC学习之自定义IP核的学习与设计】】

萌新的SOC学习之自定义IP核的学习与设计 本章为了更加深入的了解FPGA的自定义IP和IP封装测试等问题 参考了正点原子 第六讲自定义IP核呼吸灯实验 和 第十九章 IP封装与接口定义实验 为了更好的理解自定义IP核 我们先介绍一个带AXI主从接口的IP核 我们可以展开AXI从接口 下…

Vue--》简易资金管理系统后台项目实战(后端)

今天开始使用 node vue3 ts搭建一个简易资金管理系统的前后端分离项目&#xff0c;因为前后端分离所以会分两个专栏分别讲解前端与后端的实现&#xff0c;后端项目文章讲解可参考&#xff1a;前端链接&#xff0c;我会在前后端的两类专栏的最后一篇文章中会将项目代码开源到我…

转行做程序员,多晚都不晚

大家好啊&#xff0c;我是董董灿。 最近有不少小伙伴加我微信咨询一些问题&#xff0c;有同学想了解AI行业的现状&#xff0c;想着转行的&#xff0c;也有在校生想了解毕业后工作方向的&#xff0c;当然也有想学习编程知识的。 诚惶诚恐&#xff0c;没想到之前写的文章&#…

【CHI】CHI协议,transaction事务汇总

前言 CHI协议最难的是什么&#xff0c;就是那一堆各种各样的事务&#xff0c;你不知道什么场景应该使用什么合适的事务&#xff0c;收到X事务又该回复什么事务。相当于CHI给你制定了很多种&#xff08;尽可能覆盖完全&#xff09;场景及事务&#xff0c;你需要去了解&#xff0…

英语——分享篇——每日200词——2401-2600

2401——moisture——[mɔɪstʃə(r)]——n.潮气&#xff0c;湿气&#xff0c;水分——moisture——moist潮湿的(熟词)ur你的(编码your)e鹅(编码)——潮湿的地方你的鹅一身潮气——Moisture in the atmosphere condensed into dew during the night.——大气中的水分在夜间凝结…

二阶系统时域响应

二阶系统微分方程 二阶系统传递函数 二阶系统单位阶跃响应 过阻尼系统 临界阻尼系统 欠阻尼系统 无阻尼系统 二阶系统阶跃响应仿真 在Matlab中进行仿真&#xff0c;设置不同阻尼比2、1、0.5和0&#xff0c;可以得到结论&#xff1a; 阻尼比越小&#xff0c;系统响应速度越快&…

YOLOV8改进:RefConv(即插即用!重参数化重聚焦卷积替代常规卷积,无额外推理成本下涨点明显!)

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 3.涨点效果:RefConv,实现有效涨点! 论文地址…

【项目设计】网络对战五子棋(上)

想回家过年… 文章目录 一、项目前置知识1. websocketpp库1.1 http1.0/1.1和websocket协议1.2 websocketpp库接口的前置认识1.3 搭建一个http/websocket服务器 2. jsoncpp库3. mysqlclient库 二、 项目设计1. 项目模块划分2. 实用工具类模块2.1 日志宏封装2.2 mysql_util2.3 j…