C++多态(+多态原理超详解哦)

news2025/1/24 17:57:31

C++多态

  • 引言
  • 定义及实现
    • 多态的条件
    • 虚函数与虚函数的重写
    • 接口继承与实现继承
    • 函数重载,隐藏,重写的区别
  • 抽象类
  • 多态的原理
    • 虚函数表(虚表)
    • 动态绑定与静态绑定
  • 总结

引言

在生活中不乏这样的例子:成人与儿童在买票时会有不同的价格,儿童可以半价买票;又如大家耳熟能详的电商企业“并夕夕”,新用户与老用户在扫同样的福利二维码时,会获得不同的收益。

这种同样的事件对不同种类的对象会产生不同的状态的不同对象执行多种状态的行为,就是多态

在上一篇中详细介绍了继承的行为, 继承中的父类与子类就是天然的两种不同对象,并且有着密切的联系。所以多态就天然的在继承下实现。戳我see继承详解哦

定义及实现

为方便叙述,我们直接引入基类Person类,与派生类Children类。相同的事件就是类中的同名函数(就是虚函数,马上会介绍),通过基类的指针或引用调用这个 “ 相同事件 ” 时,就会产生不同的状态:

class Person
{
public:
	virtual void pay(int x)
	{
		cout << "person pay:" << x << endl;
	}
};
class Children : public Person
{
public:
	virtual void pay(int x)
	{
		cout << "children pay:" << x / 2 << endl;
	}
};

void buyTickets(Person& person, int price)
{
	person.pay(price);
}

int main()
{
	int price = 100;
	Person person;
	Children child;

	buyTickets(person, price);
	buyTickets(child, price);

	return 0;
}	

在这里插入图片描述

多态的条件

由上面的例子,我们可以很容易的获得多态的条件:

  • 基类必须定义有虚函数,并且派生类要对虚函数进行重写;
  • 必须由基类的指针或引用调用虚函数。

第二个条件其实很好理解:由如果由派生类的指针或引用来调用的话,由于这个指针只可能指向派生类,所以只可能调用到派生类中重写后的虚函数;而由于继承中天然的切片行为,基类的指针可能指向基类,也可能指向派生类,这就给了基类的指针或引用了调用基类中虚函数与派生类中重写的虚函数的条件

而第一个条件中的必须有虚函数:可以理解为虚函数的定义与重写区分了指向基类的基类指针指向派生类的基类指针。从而使多态行为可行(这其中的原理后面就会讲到)。

虚函数与虚函数的重写

在继承中存在成员函数的隐藏,即派生类中的函数会隐藏基类中的同名函数,如果一个基类指针指向一个派生类对象,由于切片的效果,通过这个基类指针来访问元素时,访问的依旧是基类的元素。
但是由于虚函数的存在,我们通过基类的指针或引用访问到的就是它实际指向的对象的成员函数。

虚函数,就是被关键字virtual修饰的函数:
virtual 返回值类型 函数名 (参数列表);

例如上面基类中的虚函数:

	class Person
	{
	public:
		virtual void pay(int x)
		{
			cout << "person pay:" << x << endl;
		}
	};

虚函数的重写是指派生类中存在与基类中虚函数返回值类型、函数名、参数列表完全相同的虚函数,例如上面派生类中的虚函数:

	class Children : public Person
	{
	public:
		virtual void pay(int x)
		{
			cout << "children pay:" << x / 2 << endl;
		}
	};

虚函数的重写存在着两个特例

  1. 协变(重写返回值不同):
    当虚函数需要返回当前类对象的指针或引用时,可以基类虚函数返回基类的指针或引用,派生类虚函数返回派生类的指针或引用,称为协变:
	class A
	{
	public:
		A(int a = 0)
			:_a(a)
		{}
		virtual A* func() //返回基类指针
		{
			cout << _a << endl;
			return new A(_a + 1);
		}
	public:
		int _a;
	};
	class B : public A
	{
	public:
		B(int a = 0, int b = 0)
			:A(a)
			,_b(b)
		{}
		virtual B* func() //返回派生类指针
		{
			cout << _a << " " << _b << endl;
			return new B(_a + 1, _b + 1);
		}
	public:
		int _b;
	};
  1. 析构函数的重写(重写函数名不同):
    当基类中的析构函数为虚函数时,派生类中定义的析构函数一定为虚函数。将析构函数定义为虚函数,可以使我们通过基类的指针或引用释放派生类空间时能够正确释放,不造成内存泄漏。
    但是,基类的析构函数与派生类析构函数的函数名是不同的。其实编译器会将析构函数的函数名统一处理为destructor,以应对这里的函数名不相同的问题:
	class A
	{
	public:
		virtual ~A()
		{
			cout << "destruct A" << endl;
		}
	};
	class B : public A
	{
		virtual ~B()
		{
			cout << "destruct B" << endl;
		}
	};
	
	int main()
	{
		A* pa = new A;
		A* pb = new B;
	
		delete pa;
		cout << endl;
		delete pb;
	
		return 0;
	}

在这里插入图片描述

接口继承与实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,相当于将基类搬到派生类中,在派生类中可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,相当于将函数的原型继承下来(虚函数的要求即函数原型相同),而函数的饿实现由派生类重写,继承的是函数接口。

函数重载,隐藏,重写的区别

函数重载指在同一域中,存在函数名相同,但是函数返回值类型或参数列表不同的多个函数,调用重载的函数时,总是匹配最合适的那个重载函数;

函数隐藏是指在基类与派生类作用域中存在同名函数,基类中的那个同名函数会被隐藏;

函数重写是指在基类与派生类作用域中存在两个函数名、返回值与参数列表完全相同的虚函数,运用在多态中。

抽象类

在虚函数的后面加上 =0,表示这个函数为纯虚函数,包含纯虚函数的类叫做抽象类。
抽象类不能实例化对象,只有当派生类重写该纯虚函数后,才可以使用这个派生类实例化对象:

class A
{
public:
	virtual void Func() = 0;
};
class B : A
{
public:
	virtual void Func()
	{
		cout << "B" << endl;
	}
};

int main()
{
	//A a; 错误代码,抽象类不能被实例化
	B b;
	return 0;
}

多态的原理

class A
{
public:
	virtual void func1()
	{
		cout << "A1" << endl;
	}
	virtual void func2()
	{
		cout << "A2" << endl;
	}
	virtual void func3()
	{
		cout << "A3" << endl;
	}
public:
	int _a = 10;
};
class B : public A
{
public:
	virtual void func1()
	{
		cout << "B1" << endl;
	}
	virtual void func2()
	{
		cout << "B2" << endl;
	}
	virtual void func3()
	{
		cout << "B3" << endl;
	}
public:
	int _b = 20;
};

void testFunc(A* pa)
{
	pa->func1();
	pa->func2();
	pa->func3();
}

int main()
{
	A a;
	B b;

	testFunc(&a);
	testFunc(&b);

	return 0;
}

在这里插入图片描述

虚函数表(虚表)

当在类中定义虚函数后,在实例化的基类时,会定义一个函数指针数组,这个函数指针数组就是虚函数表,虚表的指针存在对象中(vfptr——virtual function pointer,通常以nullptr为止):

  • 在基类中对象中,包含虚表的指针及其成员变量。虚函数表中包含基类中虚函数的函数指针
  • 在派生类对象中,包含基类的部分,派生类的虚表指针及成员变量。派生类虚表的指针会替换基类中的虚表指针,指向的虚表中包含重写后的虚函数的函数指针

在这里插入图片描述

在了解虚表之后,要解释多态的原理就变得很简单了,当我们在通过基类的指针或引用调用虚函数时:

  • 对于指向基类对象的基类指针,它指向空间中的虚表中的函数指针是基类中的虚函数指针

  • 对于指向派生类对象的基类指针,发生切片后,它指向空间中的虚表中的函数指针是派生类中重写过的虚函数指针

在这里插入图片描述

而普通的继承不能实现多态的原因就在这里,当切片后,不论指向基类对象还是派生类对象的基类指针都只能访问到基类中的成员函数。

动态绑定与静态绑定

刚才提到的多态行为,实在运行时才确定基类的指针是指向基类对象还是派生类对象的。即运行时根据拿到的类型确定要调用哪个函数。这样的行为称为动态绑定,或动态多态;
我们在之前的重载也是一种多态行为,只是它在编译阶段就确定了要调用哪个函数。这样的行为称为静态绑定,或静态多态。

总结

到此,关于多态的相关知识就介绍完了

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

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

相关文章

Java基础之并发篇(二)

1、前言 本篇主要基于Java基础之并发篇&#xff08;一&#xff09;继续梳理java中关于并发相关的基础只是。本篇基于网络整理&#xff0c;和自己编辑。在不断的完善补充哦。 2、synchronized 的原理是什么? synchronized是 Java 内置的关键字&#xff0c;它提供了一种独占的…

vue中组件的传递取值方式总结

vue中组件的传递取值方式总结 目录概述需求&#xff1a; 设计思路实现思路分析1.父子传参2.vue 子组件接收数据进行渲染或处理3.父组件通过v-on监听自定义事件并取得传递的值4.ref引用子组件5.通过provide/inject传值6.attrs和listeners7.通过vuex进行全局状态管理8.Vue mixin9…

快速查询自己哔哩哔哩账号的注册时间

登录自己哔哩哔哩 访问下面地址 https://member.bilibili.com/x2/creative/h5/calendar/event?ts0 打开后&#xff0c;在网页中查找“jointime”&#xff0c;join time&#xff0c;“加入时间”&#xff0c; 如下图&#xff0c;“jointime”冒号后面的一串数字&#xff0c;是…

汽车ECU的虚拟化技术初探(四)--U2A内存管理

目录 1.内存管理概述 2. 内存保护功能 2.1 SPID 2.2 Slave Guard 3.小结 1.内存管理概述 为了讲清楚U2A 在各种运行模式、特权模式下的区别&#xff0c;其实首先应该搞清楚不同模式下可以操作的寄存器有哪些。 但是看到这个寄存器模型就头大。 再加上之前没有研究过G4MH…

网页设计-用户体验

Use Cases (用例) 用例是用户如何在网站上执行任务的书面描述&#xff0c;从用户的角度描述了系统响应请求时的行为。每个用例都是用户实现目标的一系列简单的步骤。简言之&#xff0c;用例是一种用于描述系统如何满足用户需求的方法。 用例的好处 1. 明确需求&#xff1a; Use…

掌握WPF控件:熟练常用属性(二)

WPF布局常用控件&#xff08;二&#xff09; Calendar 用于日期选择的控件。它提供了一个可视化的界面&#xff0c;可以通过它来选择特定的日期。 常用属性描述DisplayMode用来设置Calendar的显示模式&#xff0c;有三种可选值&#xff1a;默认Month&#xff08;月&#xff…

详解SpringCloud微服务技术栈:认识微服务、服务拆分与远程调用

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;首期文章 &#x1f4da;订阅专栏&#xff1a;微服务技术全家桶 希望文章对你们有所帮助 在此之前&#xff0c;耗时半个月&#x…

高效学习新编程语言的实践指南

学习一门新的编程语言或框架对于新手和有经验的程序员来说都是一个挑战。然而&#xff0c;通过采用一些有效的策略和方法&#xff0c;我们可以快速入门并掌握新的编程工具。本文将从新手和有编程经验两个不同的角度&#xff0c;分享一些实用的建议和技巧&#xff0c;帮助读者在…

Echarts图表如何利用formatter自定义tooltip的内容和样式

在展示多数据图表的时候 有的时候需要图例也展示出一些内容来&#xff0c;例如官方这样子&#xff1a;鼠标悬停的时候展示该点数据 但是&#xff0c;官方提供的样式有时不适用所有的开发场景 我的项目需要实现鼠标悬停在某一点的时候&#xff0c;只展示该条线的数据&#xff0…

【python】进阶--->MySQL数据库(三)

一、修改列的类型长度及约束 alter table 表名 modify 列名 类型(长度) [约束];修改列名 : alter table 表名 change 旧列名 新列名 类型(长度) [约束];二、数据查询语言 查询表中所有的数据 : select * from 表名; 查询表中部分列数据 : select 列名1, 列名2 from 表名;1. …

长亭科技-雷池WAF的安装与使用

目录 1、安装雷池 2、登录雷池 3、简单配置 4、防护测试 5、其他补充 1、安装雷池 在Linux系统上执行如下命令 &#xff08;需要docker环境&#xff0c;提前把docker、docker-compose 装好&#xff09; bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release…

MySQL8.0安装(Linux - centos)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 其他…

企业异地访问办公系统:对比运营商MPLS专线,内网穿透有何优势?

为了实现连锁门店、企业内部各地分支机构ERP、OA、远程监控、自建邮件服务器、智能网络设备等数据传输、互访&#xff0c;使用运营商专线或是采用内网穿透方案&#xff0c;彼此之间究竟有何区别呢&#xff1f; 简单来说&#xff0c;MPLS专线和普通宽带类似是运营商提供的网络租…

【GitHub项目推荐--国外大神复刻暗黑2】【转载】

《暗黑破坏神2》&#xff0c;由顶尖游戏公司暴雪研发&#xff0c;2000 年上市&#xff0c;其资料片 2001 年上市&#xff0c;2D 画面。相信这款游戏已经成为很多人的回忆了&#xff0c;不知道当时是不是也和我一样沉迷于收集套装呢&#xff1f; 这款游戏的剧情设计、画面感都令…

js关闭当前窗口报错Scripts may close only the windows that were opened by them

文章目录 一、问题二、原因三、解决四、最后 一、问题 在Chrome浏览器中调用window.close()关闭当前页面时浏览器控制台报出 Scripts may close only the windows that were opened by them. 且无法关闭当前页面。 先上结论&#xff1a;不是通过JS打开的浏览器标签&#xff0c…

HTML--CSS--边框、列表、表格样式

边框样式 属性&#xff1a; border-width 边框宽度 border-style 边框外观 border-color 边框颜色 需要同时设定三个属性 border-width 边框宽度 取值为像素值 border-style 边框样式 none 无样式 dashed 虚线 solid 实线 border-color 边框颜色 如示例&#xff1a; 为div设…

知识点整理[(GraphGeo)RELATED WORK]

2 RELATED WORK 2.1 IP Geolocation 问题一:IP定位预测方法之一:Data mining-based methods 回答: 依赖于在公开的资源中挖掘位置线索来对目标IP(target IP)进行地理定位。其中一些数据分析了来自与IP相关的数据库,如WHOIS数据库和DNS的数据。 (1)例如,Moore等…

如何简单的使用文心一言(高级版)(中国版ChatGPT)

文心一言API高级版使用 一、百度文心一言API(高级版)二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例5、智能生成API代码 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、百度文心一言API(高级版) 基于百度文心一言语言大模型的…

NLP论文阅读记录 - WOS | 2022 使用语言特征空间的抽象文本摘要的神经注意模型

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作三.本文方法3.1 总结为两阶段学习3.1.1 基础系统 3.2 重构文本摘要 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Neural A…

SMART PLC绝对值定位往复运动控制(脉冲绝对定位+状态机编程)

三菱FX3GA系列绝对定位指令DDRVA实现往复运动控制详细内容介绍请参考下面文章链接&#xff1a; https://rxxw-control.blog.csdn.net/article/details/135570157https://rxxw-control.blog.csdn.net/article/details/135570157这篇博客我们介绍SMART PLC里如何开启绝对值定位指…