C++:多态

news/2024/5/19 12:28:57

目录

概念:

多态产生的条件:

 虚函数的重写:

虚函数:即被virtual修饰的类成员函数称为虚函数 

虚函数重写的两个例外:

 协变(基类与派生类虚函数返回值类型不同)

析构函数 

而为什么没有调用到子类呢?

注意子类不写virtual父类加上了virtual也可也进行虚函数的重写,但是不太建议

 重写、重定义、重载区别

继承体系区分:

原型的区分:

多继承中的指针偏移问题:

 虚表与虚表指针:

虚表指针概念: 

指向谁调用谁: 

指向谁调用谁,传父类调用父类,传子类调用子类: 

 指向谁调用谁:

 传父类调用父类,传子类调用子类:

单继承的虚表状态: 

不过这里可以解释为一种bug:



概念:

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

举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。 再举个栗子: 最近为了争夺在线支付市场,支付宝年底经常会做诱人的扫红包-支付-给奖励金的 活动。那么大家想想为什么有人扫的红包又大又新鲜8块、10块...,而有人扫的红包都是1毛,5 毛....。其实这背后也是一个多态行为。

多态产生的条件:

  1.  父子类之间完成虚函数的重写
  2. 父类的指针或者引用去调用虚函数,且子类必须对父类的虚函数进行重写操作
 虚函数的重写:

父类要有虚函数,子类也需要有虚函数,且父子类虚函数是同一个函数名,统一的参数(参数的缺省值可以不同),父类和子类的虚函数返回值是要一致的。

而父类指针或者引用调用虚函数,就如下图所示:

虚函数:即被virtual修饰的类成员函数称为虚函数 
  1. 尤其是父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态
  2. 同时静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名:.成员函数名 直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数

虚函数重写的两个例外:

 协变(基类与派生类虚函数返回值类型不同)

 派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

 协变就是父子类的虚函数返回值不同,不过返回值必须是父子类关系的指针或者引用

如图所示,类A是类B的父类,而person中的虚函数使用了返回值类型是A*而他的子类student的虚函数使用的返回值是B* 刚好AB是父子关系,所以这就变成了协变

当然这个父子类指针是可以指其他类型的父子类,也可也是自己的父子类。

析构函数 

 父类和子类的虚函数的析构函数函数名并不相同,但是因为析构函数在多态中会被编译器在暗地里变成同一个名字。

为什么会变成同一个名字?因为析构函数会因为父子类关系,在子类调用析构后会自动调用父类,但是:

person是一个类指针,既可以指向自己也就是父类,又可以指向子类,这是因为切片的概念(这里的父子类没有变成多态关系) ,而在使用delete调用析构时出了问题!没有调用子类的析构,即使我们指向了子类。

而为什么没有调用到子类呢?
  • 因为delete的内部构成是:析构函数和operator delete()
  • 而operator delete 有一个特点,那就是调用delete 的指针是什么类型的,就会调用什么类型的析构函数,而这里的两个指针都是父类person 所以就都调用了父类person的析构函数,所以没有调动子类的析构函数

所以这样子写就会有一种后果: 

在子类的析构函数还有东西,例如一个新的空间,所以如果只调用了父类,那么子类内部构建的东西就会变成内存泄露,内部有空间没有释放!

所以想要指向父类调用父类析构,指向子类调用子类析构,在希望出现这种情况时,就需要把析构函数的名字进行了多态暗地里的统一,变成了destructor,所以加上了虚函数加上destructor就变成了重写。

注意子类不写virtual父类加上了virtual也可也进行虚函数的重写,但是不太建议

 重写、重定义、重载区别

继承体系区分:
  • 重写确实发生在继承体系中,子类重写父类的虚函数。
  • 重定义通常指的是在派生类中定义一个与基类同名的成员,这可以发生在继承体系中,但它与重写不同,因为重写特指虚函数的覆盖。 
  • 重载是发生在同一个类中的,它指的是在同一个作用域内使用相同的函数名但参数列表不同的函数。重载与继承体系无关,重载只能在一个范围内,不能在不同的类里
原型的区分:
  • 重载要求函数名相同但参数列表不同。
  • 重写要求函数名和参数列表都与基类中被重写的虚函数相同。
  • 重定义的原型可能与基类中的成员不同,但这并不是关键区别点。

多继承中的指针偏移问题:

 答案选C,p1和p3指向的位置是相等的,但只是巧合,这里根据的是切片原理,p1只是指向第一个方形区域,p2指向的是第二个方形区域,而p3是指向两个方形区域外面的大方形区域!

 虚表与虚表指针:

虚表指针概念: 

  1. 虚表指针(vptr)并不是每个虚函数一个虚表指针。
  2. 实际上,每个包含虚函数的类     的   对象   都有一个虚表指针(vptr)而不是每个虚函数一个。
  3. 这个虚表指针指向一个虚函数表(vtable),虚函数表中存放了该类所有虚函数的地址。
  4. 当子类继承父类并覆盖父类的虚函数时,子类的虚函教表会包含父类的虚函数地址(如果子类没有覆盖这些函数)以及子类新增或覆盖的虚函数地址。
指向谁调用谁: 
  • 父类对象的虚表与子类对象的虚表没有任何关系,这是两个不同的对象,这也是虚函数中,指向谁调用谁的概念本质。
  • 通过虚表指针和虚函数表,就可以实现多态性,即可以在运行时确定应该调用哪个类的虚函数。
  • 虚表指针是类级别的,而不是函数级别的。
  • 每个类只有一个虚表指针,指向其对应的虚函数表,但是多继承的时候,就会可能有多张虚表。
  • 每一个类中的虚函数在虚表中都要有地址。

指向谁调用谁,传父类调用父类,传子类调用子类: 
 指向谁调用谁:

1.父类和子类的func构成了重写,即使子类的func没有加上virtual,但是还是具有重写,别看参数的缺省值不一样,但只要参数一样就行,所以父类和子类的func构成了重写

2.给了一个test加上了virtual,然后子类指针遍历p调用了父类的test,这是因为继承所以可以调用父类,同时,这里面test内部的指针是A*this,而不是B*this,因为子类是继承不是拷贝,所以是调用父类的成员,所以这里面隐藏的this是父类的类型,所以这里将p传给了A*this,而this调用了func(),因为this是A所以这里是多态调用

3.换句话说,这里面的test就可以当作一个多态调用的函数了,就例如上面迈火车票的函数调用一样,可以变换成 void test(A*this){this->func()}

4.而这里传值传的p 是 指向的是 一个new B 的地址,所以只能调用相对应类型的方法,也就是B的方法,从而调用了B的func()

答案是B::f(),

虽然 B 的 f 函数被声明为 private,但这并不影响多态性的工作。当您使用基类的指针或引用来调用虚函数时,C++ 运行时系统会检查对象的实际类型,并调用正确的函数。访问权限(如 publicprotectedprivate)仅影响代码在何处可以访问该函数,而不影响多态性的行为。

因此,即使 B 的 f 函数是 private 的,当通过基类指针 pa 调用 f 函数时,由于 pa 实际上指向一个 B 对象,所以仍然会调用 B 的 f 函数。这就是为什么输出是 B::f()

 传父类调用父类,传子类调用子类:

如图所示, 图中父子类各自的虚表指针指向的虚表以及虚表内部的地址并不相同,所以这里证明了父类的虚表指针和子类的虚表指针指向的虚表并不是同一个。

并且,对于编译器而言在指向谁调用谁 在使用这个指令之前,编译器会自行的判断这个父子类是否是产生了多态,如果是则编译器会如上图所示,如果不是编译器则会如下图所示:

单继承的虚表状态: 

如图所示,只有f1进行了多态的重写,func2没有重写因为子类没有func2 ,func3没有重写因为父类么有func3。

父类虚表  

子类虚表 

可以看到 子类的虚表不正常,因为每一个类中的虚函数在虚表中都要出现,所以这里少了两个虚函数的地址,func1是重写的,func2是继承的没有重写,而func3和func4不见了!作为子类自己的虚函数消失了。

不过这里可以解释为一种bug:

因为虚函数都需要进入虚表内部,但这里并没有放入,这里是VS监视窗口的bug

也可也理解为,子类的虚表实际上是拷贝了父类的虚表,重写的部分进行覆盖,没有重写的部分不变动的拷贝,也可也说是子类的虚表被隐藏了。

 final

如果不想要一个类被继承就加上final,如图外面不想要car被继承就加上了final,加上final后类不能被继承,虚函数不能被重现,被final修饰的类被叫做最终类。

 oberride:

是写在子类的重写虚函数后边的,是可以帮忙检查是否完成重写,如果没有完成重写会报错,或者在重写时会出现一些问题时,overrdie会发出报错!

 


 

 


http://www.mrgr.cn/p/12048410

相关文章

查找链表中倒数第k(k为正整数)个位置上的结点,查找成功输出该结点的data值,并返回1,否则只返回0

/******************************************************** name : FindKNode* function : 查找链表中倒数第k(k为正整数)个位置上的结点* 查找成功输出该结点的data值,并返回1,否则只返回0* argument* @head : 链表头结点的地址*…

杂货铺 | KVM虚拟化环境的配置 初步实现两种Guest OS虚拟机的部署

文章目录 📚在ubuntu系统的虚拟机上挂载CentOS操作系统的客户机🐇下载镜像并配置虚拟机🐇开启虚拟机,检查CPU是否支持虚拟化🐇查看是否加载KVM模块🐇关闭selinux🐇安装KVM相关软件包&#x1f40…

DRF之View和APIView

【零】DRF在Django项目中的使用 【1】导入 # DRF需要使用pip install 安装 pip install djangorestframeworkDRF(Django Rest Framework)是一个用于构建 Web API 的工具包,它是基于 Django 框架的一个第三方应用(app) 在 Django 项目中,一个应用(app)通常是一个具有特定…

删除顺序表L中下标为p(0≤p≤length-1)的元素,成功返回1,否则返回0,并将被删除元素的值赋给e

删除顺序表L中下标为p(0≤p≤length-1)的元素,成功返回1,否则返回0,并将被删除元素的值赋给e/******************************************************************************************************** * * file name: Zqh_splist_4.22.2.c * author : keyword2024…

HarmonyOS NEXT应用开发案例—使用弹簧曲线实现抖动动画及手机振动效果案例

介绍 本示例介绍使用vibrator.startVibration方法实现手机振动效果,用animateTo显示动画实现点击后的抖动动画。 效果图预览使用说明加载完成后显示登录界面,未勾选协议时点击一键登录按钮会触发手机振动效果和提示文本的抖动动画。实现思路创建一个函数startVibrate()调用vi…

删除最小值结点

/******************************************************** name : DelTargetNode* function : 删除单链表L(有头结点)中的一个最小值结点* argument* @L :链表头结点的地址** retval : None* author : Dazz* date : 2024/4/22* …

【C++】适配器· 优先级队列 仿函数 反向迭代器

目录 适配器:适配器的应用:1. 优先级队列:仿函数:更深入的了解仿函数:一个关于不容易被注意的知识点: 2. 反向迭代器:(list为例) 适配器: 我们先来谈来一下容…

Hive 解决数据倾斜方法

数据倾斜问题, 通常是指参与计算的数据分布不均, 即某个 key 或者某些 key 的数据量远超其他 key, 导致在 shuffle 阶段, 大量相同 key 的数据被发往同一个 Reduce, 进而导致该 Reduce 所需的时间远超其他 Reduce&…

vis.js性能折线图

代码案例<!doctype html> <html> <head><title>Timeline</title><script type="text/javascript" src="https://unpkg.com/vis-timeline@latest/standalone/umd/vis-timeline-graph2d.min.js"></script><scr…

计算请假时间,只包含工作时间,不包含中午午休和非工作时间及星期六星期天,结束时间不能小于开始时间

1.计算相差小时&#xff0c;没有休息时间 computed: {// 计算相差小时time() {let time 0;if (this.ruleForm.date1 &&this.ruleForm.date2 &&this.ruleForm.date3 &&this.ruleForm.date4) {// 开始时间let date1 this.ruleForm.date1;let y date…

设计一个算法删除单链表L(有头节点)中的一个最小值结点

数据结构 链表 笔试题:设计一个算法删除单链表L(有头节点)中的一个最小值结点。/***************************************************************** * * file name : linkedlist.c * author : cnzycwp@126.com * data : 2024/04/22 * function : 删除单链表中的一…

docker服务无法启动

背景&#xff1a;断电重启经常会导致磁盘io错误&#xff0c;甚至出现磁盘坏块 这时可以使用xfs_repair来修复磁盘&#xff0c;但是修复过程可能会导致部分数据丢失 xfs_repair -f -L /dev/sdc问题一&#xff1a; Apr 15 19:27:15 Centos7.6 systemd[1]: Unit docker.service e…

Visual Studio 2022 Professional、Enterprise安装教程

Visual Studio 2022 Professional、Enterprise安装教程 下载安装包安装 我是电脑已经有VS2019&#xff0c;现在加装一个VS2022。 下载安装包 首先下载安装包&#xff0c;进入官网进行下载&#xff0c;VS官网下载地址。 进入之后&#xff0c;会显示如下界面&#xff0c;选择Pro…

linux-rpm包管理-命名-管理

1.RPM基础概述 RPM全称 RPM Package Manager 缩写,由红帽开发用于软件包的安装,升级卸载与查询 为什么要学rpm就像在windows系统中一样,如果你想要安装一个 QQ ,安装一个 微信 ,安装一款 游戏 ,首先要去该软件的官网上去下载相关的软件包,通常都是 .exe 的安装包。还有那…

力扣110. 平衡二叉树

思路&#xff1a;与二叉树最大高度类似&#xff0c;但是这里需要返回 -1 的高度来标识不是平衡二叉树&#xff0c;判断左右子树的高度相差大于1则不平衡&#xff0c;否则就是平衡。 class Solution {public boolean isBalanced(TreeNode root) {int ans func(root);if(ans >…

最强开源大模型Meta LIama3抢先在线体验!

4月19日Facebook母公司Meta重磅推出了其迄今最强大的开源人工智能&#xff08;AI&#xff09;模型——Llama 3。模型分为两种规模&#xff1a;8B 和 70B 参数&#xff0c;每种规模都提供预训练基础版和指令调优版。最强开源大语言模型Meta LIama3可以在线体验啦&#xff01; G…

【运输层】TCP 的流量控制和拥塞控制

目录 1、流量控制 2、TCP 的拥塞控制 &#xff08;1&#xff09;拥塞控制的原理 &#xff08;2&#xff09;拥塞控制的具体方法 1、流量控制 一般说来&#xff0c;我们总是希望数据传输得更快一些。但如果发送方把数据发送得过快&#xff0c;接收方就可能来不及接收&#x…

TensorFlow文件读取 --TFRecords文件

TFRecords文件 是一种二进制文件&#xff0c;能够很好的利用内存&#xff0c;更方便复制和移动&#xff0c;并且不需要单独的标签文件 使用步骤 1&#xff09;获取数据 2&#xff09;将数据填入到Example协议内存块&#xff08;protocol buffer&#xff09; 3&#xff09;将协…

vis.js本地化折线图

代码案例<!doctype html> <html> <head><title>Timeline</title><script type="text/javascript" src="https://unpkg.com/vis-timeline@latest/standalone/umd/vis-timeline-graph2d.min.js"></script><lin…

【每周例题】力扣 C++ 分割字符串

分割字符串 题目 题目分析 1.先确定用容器存储,容器的存储结构如下图所示: 2.这个题目的话,第一反应应该是用到动态规划,下面是动态规划的模板:res = [] ans = []def backtrack(未探索区域, res, path):if 未探索区域满足结束条件:res.add(ans) # 深度拷贝returnfor 选择 …