c++多态及其多态的原理

news2024/11/24 10:57:04

目录

    • 多态的定义及实现
      • 多态的条件
    • 多态的原理
      • 虚函数表
      • 动态绑定和静态绑定
      • 总结多态原理
    • 单继承和多继承下的虚函数表
      • 单继承下的虚函数表(有虚函数覆盖)
      • 多继承下的虚函数表(有虚函数覆盖)
      • 为什么子类对象赋值给父类对象,也可以切片,但为什么实现不了多态

我们知道面向对象的三大特性是:封装、继承和多态。
封装:就是隐藏类的内部细节,只提供对外开放的接口;举个例子,就是你去餐厅吃饭,你需要到前台点餐或者是扫二维码点餐;前台就是对外开放的接口,你不用管具体的细节

继承:就是面向对象设计使代码可以复用的重要手段,它可以让我们在保持原有类特性的基础上进行扩展,增加功能;你可以把公共的属性提取出来封装成一个类,再让别的类去继承它;

多态:简单来说多态就是同一个行为具有不同的表现方式;具体点就是多态就是在继承的基础上,重写父类中成员函数并定义父类引用指向子类对象;当你用一个父类引用去接收一个子类对象,并且这个子类重写了父类的成员函数,那么当你调用这个函数时,就是调用子类重写后的函数;这就是一个行为多种表现方式

多态的定义及实现

多态的条件

构成多态有两个条件:

  1. 子类虚函数重写父类虚函数(重写:三同(返回值类型、函数名字、参数列表完全相同)+虚函数)

  2. 父类指针或引用接收子类对象,然后取调用虚函数
    其实多态的定义上面已经讲得很清楚了,那么这里再总结一下吧!
    多态其实就是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
    举个例子:比如说动物的叫声,同样是“叫”这个行为,不同的动物有不同的叫声。

#include <iostream>

using namespace std;


class Animal
{
public:
	virtual void cry() = 0;
	
	
};

class Cat:public Animal
{
public:
	virtual void cry()
	{
		cout << "喵喵" << endl;
	}
};

class Dog :public Animal
{
public:
	virtual void cry()
	{
		cout << "旺旺" << endl;
	}
};

int main()
{

	Cat c;
	Dog d;

	Animal& cat = c;
	Animal& dog = d;

	cat.cry();
	dog.cry();

	return 0;
}

运行结果:
在这里插入图片描述
OK,到这里,什么是多态也差不多该懂了;这里解释一下上面的代码,主要是补充纯虚函数和抽象类两个概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写。

多态的原理

接下来我们来讲多态的原理

虚函数表

我们先来认识一下什么是虚函数表,先看下面代码,猜一猜运行结果

#include <iostream>

using namespace std;

class Base
{
public:
	virtual void fun()
	{
		cout << "fun" << endl;
	}
};

int main()
{
	Base b;
	cout << sizeof(Base) << endl;
	return 0;
}

运行结果:
在这里插入图片描述
提示一下:这是在32位平台下运行的
为什么这个类的大小是4呢?
我们来看一下类的大小由什么组成

  1. 非静态成员变量的内存占用之和
  2. 考虑内存对其的问题;
  3. 虚函数产生的额外内存开销,即虚函数表指针(Virtual Table Pointer);
    看到这里我想应该明白为什么是4了吧,答案就是因为这个Base类中有虚函数fun所以,它有一个虚函数指针,这个虚函数指针指向虚函数表,虚函数表存着虚函数的地址。

打开监视窗口一看,确实是这样子的
在这里插入图片描述

知道虚函数表和虚函数指针后,我们继续往下面分析

class Base1
{
public:
	virtual void fun1()
	{
		cout << "Base1:fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "Base1:fun2()" << endl;
	}
private:
	int _b1;
};

class Derive:public Base1
{
public:

	virtual void fun1()
	{
		cout << "Derive:fun1()" << endl;
	}

	virtual void fun3()
	{
		cout << "Derive:fun3()" << endl;
	}

	virtual void fun4()
	{
		cout << "Derive:fun4()" << endl;
	}
private:
	int _d;
};

int main()
{
	Derive d;
	Base1 b;
	b.fun1();

	return 0;
}

代码中Base1有虚函数:fun1(),fun2()
Derive继承了Base重写了Base1的虚函数:fun1();自己有虚函数fun3()和fun4()
我们来看一下监视窗口下的b对象
在这里插入图片描述
在这里插入图片描述

ok,通过上面的代码,我们可以得出以下几点

  • 派生类对象d中有一个虚表指针,d对象有两部分,一部分是父类继承下来的成员,另部分是自己的成员
  • 基类对象b和派生类对象d的虚表是不一样的,这里的fun1发生了重写,所以d的虚表中存的是重写的Derive::fun1,所以虚函数的重写也叫做覆盖,覆盖指的就是虚表中虚函数的覆盖。
  • fun2也被继承下来了,又是虚函数,所以放进了d的虚表
  • 虚函数表本质是一个存放虚函数指针的指针数组,所以一般情况下这个数组最后面放了一个nullptr(这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。)
    总结派生类虚表的生成:
  1. 先将基类中的虚表内容拷贝一份到派生类虚表中
  2. 如果派生类重写了基类的某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
  3. 派生类自己新增的虚函数按在派生类中的声明次序增加到派生类虚表的最后(关于这一点,肯定有人有疑惑了,为什么你上面的对象d的虚表里没有fun3和fun4呢?这里是编译器的监视窗口故意隐藏了这
    两个函数,也可以认为是他的一个小bug)
    到这里可能就有问题了
    虚函数存在哪里呢?

答:虚函数跟普通函数一样都存在代码段里

虚表存在哪里?

答:在vs下是存在代码段中

动态绑定和静态绑定

我们再来理解两个概念,动态绑定和静态绑定

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载

  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体 行为,调用具体的函数,也称为动态多态。

总结多态原理

OK,有了上面的铺垫,我们就能总结出多态的原理了
多态的原理是通过虚表指针和虚函数表来实现的,对象里存着虚函数指针,这个虚函数里面存在基类虚函数表的内容,如果派生类重写了基类的某个虚函数,在派生类的虚函数表里会用派生类自己重写的虚函数地址覆盖掉虚函数表中基类的虚函数地址;然后在运行时,去指向对象的虚函数表中查调用的函数地址,然后执行;这样就形成了多态

单继承和多继承下的虚函数表

接下来,我打算介绍一下,单继承和多继承下的虚函数表是长什么样的

单继承下的虚函数表(有虚函数覆盖)

class Base1
{
public:
	virtual void fun1()
	{
		cout << "Base1:fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "Base1:fun2()" << endl;
	}
private:
	int _b1;
};


class Derive:public Base1/*,public Base2*/
{
public:

	virtual void fun1()
	{
		cout << "Derive:fun1()" << endl;
	}

	virtual void fun3()
	{
		cout << "Derive:fun3()" << endl;
	}

	virtual void func4()
	{
		cout << "Derive:fun4()" << endl;
	}
private:
	int _d;
};

int main()
{
	Derive d;
	Base1 b;
	b.fun1();

	return 0;
}

在这里插入图片描述

对象d的对象模型:
在这里插入图片描述

多继承下的虚函数表(有虚函数覆盖)

class Base1
{
public:
	virtual void fun1()
	{
		cout << "Base1:fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "Base1:fun2()" << endl;
	}
private:
	int _b1;
};

class Base2
{
	virtual void fun1()
	{
		cout << "Base2:fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "Base2:fun2()" << endl;
	}
};

class Derive:public Base1,public Base2
{
public:

	virtual void fun1()
	{
		cout << "Derive:fun1()" << endl;
	}

	virtual void fun3()
	{
		cout << "Derive:fun3()" << endl;
	}

	virtual void func4()
	{
		cout << "Derive:fun4()" << endl;
	}
private:
	int _d;
};

int main()
{
	Derive d;
	
	Base1& b1 = d;
	Base2& b2 = d;
	return 0;
}

在这里插入图片描述

对象b1的对象模型:
在这里插入图片描述

对象b2对象模型:
在这里插入图片描述
向上转型会切片,所以不管是b1还是b2都访问不到子类独有的部分和其他父类的部分,简单来说向上转型后b1只能访问Base1的东西,查的也是Base1的vfptr;

这里再补充一点:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

对于菱形继承的虚函数表,这里就不展开了,下次有机会再补充

为什么子类对象赋值给父类对象,也可以切片,但为什么实现不了多态

最后再分享一下这个问题

#include <iostream>

using namespace std;

class Base1
{
public:
	virtual void fun1()
	{
		cout << "Base1:fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "Base1:fun2()" << endl;
	}
private:
	int _b1;
};

class Base2
{
	virtual void fun1()
	{
		cout << "Base2:fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "Base2:fun2()" << endl;
	}
};

class Derive:public Base1,public Base2
{
public:

	virtual void fun1()
	{
		cout << "Derive:fun1()" << endl;
	}

	virtual void fun3()
	{
		cout << "Derive:fun3()" << endl;
	}

	virtual void func4()
	{
		cout << "Derive:fun4()" << endl;
	}
private:
	int _d;
};

int main()
{
	Derive d;
	
	
	Base1 b3 = d;
	return 0;
}

在这里插入图片描述

核心点就是一个类只有一张虚表 每个对象都是有一个虚表指针 指向这张虚表
你父类对象里面存的指针很肯定是指向父类的虚函数表 跟子类无关 所以无法形成多态。

最后如果文中有什么错误的地方,请大家指正。如果有所收获,请各位给作者一键三连

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

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

相关文章

耕耘不缀、丰收如期,中药入血组又双叒叕升级了

春去夏来&#xff0c;小满已至&#xff0c;麦浪序曲逐渐拉响&#xff0c;百趣人在中药领域的勤耕不辍也迎来了本年度的第一次丰收。中药入血组作为中药临床应用重磅产品&#xff0c;此次升级可谓“一优化&#xff0c;两提升&#xff0c;三新增”。“一优化”为分析图例优化&…

Python之pyecharts的常见用法1-水球图

Pyecharts是一个基于Echarts的Python可视化库&#xff0c;可以用Python语言轻松地生成各种交互式图表和地图。它支持多种图表类型&#xff0c;包括折线图、柱状图、散点图、饼图、地图等&#xff0c;并且可以通过简单的API调用实现数据可视化。 Pyecharts的优点包括&#xff1a…

Spring Cloud Alibaba 整合Seata 之概念介绍及Seata-server搭建

目录 前言 基础介绍 官方文档 模式分类 角色介绍 Seata Server 部署 - docker-compose 数据库 服务器 docker-compose.yaml nacos配置 启动 前言 Seata 是 阿里巴巴 开源的 分布式事务中间件&#xff0c;以 高效 并且对业务 0 侵入 的方式&#xff0c;解决 微服务…

过零投切开关的安科瑞低压电力电容器设计

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 摘要&#xff1a;目前&#xff0c;我们经常使用的电容器投切开关在实际的运行过程中还存在着很多问题&#xff0c;新型的过零投切开关主要利用微电子技术&#xff0c;同时企业对电路的结构等开展了进一步的优化与调整…

真的有必要定义VO,BO,PO,DO,DTO吗?

今天给大家带来一篇关于VO&#xff0c;BO&#xff0c;PO&#xff0c;DO&#xff0c;DTO的文章&#xff0c;阅读完这篇文章之后&#xff0c;希望大家对VO&#xff0c;BO&#xff0c;PO&#xff0c;DO&#xff0c;DTO有自己的见解。 1. 概念 在讲具体的概念之前&#xff0c;我们…

I.MX6ULL_Linux_驱动篇(35) linux并发与竞争

Linux是一个多任务操作系统&#xff0c;存在多个任务共同操作同一段内存或者设备的情况&#xff0c;多个任务或者中断都能访问的资源叫做共享资源&#xff0c;就和共享单车一样。在驱动开发中要注意对共享资源的保护&#xff0c;也就是要处理对共享资源的并发访问。比如共享单车…

Mybatis插件MyBatisCodeHelperPro使用

目录 MyBatisCodeHelperPro的安装&#xff08;IDEA插件的两种安装方式&#xff09; 通过IDEA连接MySQL数据库&#xff0c;方便后续的数据库操作 MyBatisCodeHelperPro的使用 1、新建项目工程测试 2、在pom文件中引入依赖&#xff08;后面测试需要用到&#xff09; 3、编写…

使用docker部署 java web项目完整记录

概述&#xff1a;基于java及vue开发的前后端web应用&#xff0c; 整套系统由 一个后端服务一个管理端前端vue一个用户端前端vue项目组&#xff0c; 涉及的中间件及第三方应用有 mysql、redis、nginx&#xff0c; 采用docker方式部署整套系统 一、docker 安装 1、参考文档&…

jira中issue状态的改变触发jenkins job构建

背景&#xff1a;想通过监控jira中 issue状态的变化去触发jenkins job的构建 在jenkins中安装插件&#xff1a;JIRA Trigger plugin. 下载地址&#xff1a;https://plugins.jenkins.io/jira-trigger/ 在Jenkins-> Manage Jenkins -> Configure System -> JIRA Trigg…

UnityVR--EventManager--事件中心2

目录 前言 事件中心的结构 EventManager事件管理器 EventType事件类型 EventListener监听及回调 EventDataBase回调时需要传递的参数 总结 前言 上一篇&#xff08;事件中心1&#xff09;中&#xff0c;简单解释了委托、事件、监听者&#xff0c;是用于管理项目运行过程…

深度学习进阶篇[9]:对抗生成网络GANs综述、代表变体模型、训练策略、GAN在计算机视觉应用和常见数据集介绍,以及前沿问题解决

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

多线程-*同步代码块*解决两种线程创建方式的线程安全问题

线程安全问题及解决 当我们使用多个线程访问同一资源&#xff08;可以是同一个变量、同一个文件、同一条记录等&#xff09;的时候&#xff0c;若多个线程只有读操作&#xff0c;那么不会发生线程安全问题。但是多个线程中对资源有读和写的操作。就容易出现线程安全问题。 举…

PyTorch 深度学习 || 专题三:PyTorch 数据的准备

PyTorch 数据的准备 1. 生成数据的准备工作 import torch import torch.utils.data as Data#准备建模数据 x torch.unsqueeze(torch.linspace(-1, 1, 500), dim1) # 生成列向量 y x.pow(3) # yx^3#设置超参数 batch_size 15 # 分块大小 torch.manual_seed(10) # 设置种子点…

国自然发文:这种行为将永久取消基金申请资格

函评阶段这些行为被基金委禁止 国家自然科学基金委重拳出击打击"打招呼"现象,出台《国家自然科学基金项目评审请托行为禁止清单》&#xff0c;禁止科研人员、依托单位、评审专家和基金委工作人员24种违规行为。 主要内容如下: 一、科研人员禁止清单(7项) 主要包括…

初阶数据结构——二叉树

目录 树的概念及结构树的概念树的相关概念树的表示树在实际中的运用 二叉树概念及结构特殊的二叉树二叉树的性质二叉树的存储结构 二叉树的顺序结构及实现二叉树的顺序结构堆的概念及结构堆的实现堆的结构&#xff1a;初始化堆&#xff1a;销毁堆&#xff1a;插入数据&#xff…

通过maven配置不同的开发环境

前言 项目有开发、测试、生产至少有这三个环境&#xff0c; 所需要的配置信息肯定不一样&#xff0c; 比如需要开发环境的时候&#xff0c;注解掉测试和生产的配置信息&#xff0c;打开开发的配置信息。 后来工作接触到新项目&#xff0c;发现是通过maven来控制加载不同的配置…

第55讲:Python函数形参、实参的语法格式汇总以及应用小案例

文章目录 1.Python函数中各种参数的汇总1.1.常规参数1.2.参数设置默认值1.3.强制传参时使用关键字传参1.4.个数可变的形参1.5.将序列中的每个元素都转换为位置实参1.6.将字典中的每个元素都转换为关键字实参 2.小案例练习Python中的各种函数参数2.1.案例一2.2.案例二 1.Python函…

React-reacte-app项目实现antD按需加载(2023)

出现的问题&#xff1a; 项目打包后&#xff0c;由于引入了antD&#xff0c;所以打包后的mani.js体积会非常大。相当于引入了全部的antD的代码。所以可以做一个优化&#xff1a;通过script标签引入antD&#xff0c;将其挂载在window上。使用antD组件的使用&#xff0c;直接从w…

2.矢量分析

目录 一.标量函数和矢量函数 二.矢端曲线 三.矢量函数导数和微分 1.导数 2.导数的几何意义 3.微分 4.矢量导数性质 5.例题 四.矢量导数的应用 1.几何应用 1.曲线的切线和法平面 2.曲面的法线和法平面 2.物理应用 3.两大典型问题 五.矢量函数的积分 如果第一章我…

WOT全球技术创新大会开幕倒计时——好内容才是永远的底气

距离6月16日-17日召开的WOT全球技术创新大会还有半个月时间&#xff0c;所有讲师、议题均已就位。本届WOT不仅囊括2023年最火的技术——AIGC、大模型、大算力&#xff0c;也有被技术人永恒关注的热点话题——多云实践、业务架构演进、效能提升。 筹备数月之久&#xff0c;50来…