C++多态(超详解哦)

news2025/1/17 21:42:34

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/1370637.html

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

相关文章

FRPS配置服务端(腾讯云)、客户端(PC电脑Windows、树莓派Debian)并设置虚拟域名

1.服务端&#xff08;腾讯云&#xff09;&#xff1a;frps.ini [common] bind_port 7000 vhost_http_port8080 vhost_https_port44344 dashboard_port 7500 privilege_token your_password subdomain_host example.com use_encryption true encryption_method tls dashb…

oracle角色管理

常用角色 CONNECT,RESOURCE,DBA,EXP_FULL_DATABASE,IMP_FULL_DATABASE 1角色可以自定义&#xff0c;语法与创建用户一样 CREATE role role1 IDENTIFIED by 123; 2授权权限给角色 --自定义角色 CREATE role role1 IDENTIFIED by 123; --授权权限给角色 GRANT create view, …

Apache Doris (六十三): Spark Doris Connector - (3)-配置型及列映射关系

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. Spark 操作Doris配置项

【软件测试】学习笔记-脚本与数据的解耦 + Page Object模型

本篇文章介绍GUI测试中两个非常重要的概念&#xff1a;测试脚本和数据的解耦&#xff0c;以及页面对象&#xff08;Page Object&#xff09;模型。 测试脚本和数据的解耦 GUI自动化测试适用的场景&#xff0c;尤其适用于需要回归测试页面功能的场景。如果在测试脚本中硬编码&a…

Fiddler工具 — 10.Statistics(统计)面板

1、Statistics介绍 Statistics 页签显示当前用户选择的 Sessions 的汇总信息&#xff0c;包括&#xff1a;选择的 Sessions 总数、发送字节数、接收字节数、响应类型的汇总表、世界各地通过不同请求方式所需的时间等。 Statistics 分页还会统计请求和响应的其他一些信息,如&a…

红帽宣布CentOS 7和RHEL 7将在2024年6月30日结束支持,企业面临紧迫的迁移压力!

2020 年红帽 (RedHat&#xff0c;已在 2019 年被 IBM 收购) 单方面宣布终止 CentOS Linux 的开发&#xff0c;此后 CentOS Linux 8 系列的更新已经在 2021 年 12 月结束&#xff0c;而 CentOS Linux 7 系列的更新将在 2024 年 6 月 30 日结束。 与 CentOS Linux 7 一起发布的 R…

网络安全B模块(笔记详解)- nmap扫描渗透测试

nmap扫描渗透测试 1.通过BT5对服务器场景Linux进行TCP同步扫描 (使用工具Nmap,使用参数n,使用必须要使用的参数),并将该操作使用命令中必须要使用的参数作为Flag提交; Flag:sS 2.通过BT5对服务器场景Linux进行TCP同步扫描 (使用工具Nmap,使用参数n,使用必须要使用的参数…

Adobe XD是什么?探索这款创新的用户体验设计工具

Adobexd是一种基于矢量的设计工具&#xff0c;主要用于设计移动和Web应用程序的用户界面(UI)。与Photoshop或ilustrator等其他Adobe产品相比&#xff0c;它相当轻。对于对快速设计和原型迭代感兴趣的界面设计师来说&#xff0c;轻量级并不是一件坏事。 在早期&#xff0c;Adob…

光缆通信有什么特点?

光缆由一个或多个光纤组成&#xff0c;每个光纤由一个非常纤细的玻璃或塑料纤维组成&#xff0c;可以传输光信号的高速数据。光缆通信具有以下特点&#xff1a; 1. 高带宽&#xff1a;光缆通信可以提供非常高的带宽&#xff0c;远远超过传统的铜缆通信。光纤的宽带特性使其能够…

了解 Node.js 的运行机制:从事件循环到模块系统(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【leetcode 2707. 字符串中的额外字符】动态规划 字典树

2707. 字符串中的额外字符 题目描述 给你一个下标从 0 开始的字符串 s 和一个单词字典 dictionary 。你需要将 s 分割成若干个 互不重叠 的子字符串&#xff0c;每个子字符串都在 dictionary 中出现过。s 中可能会有一些 额外的字符 不在任何子字符串中。 请你采取最优策略分割…

高通平台开发系列讲解(USB篇)Ubuntu 下如何使用模块

文章目录 一、查看VID、PID二、adb添加2.1、在udev下添加模块的VID2.2、重启adb服务三、虚拟串口添加(AT、Diag)沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要图解高通平台上位机使用方法 一、查看VID、PID 在ubuntu下使用模块进行AT指令发送,Diag等串…

【学术会议】第三届神经计算青年研讨会 学习笔记

第三届神经计算青年研讨会 学习笔记 会议时间&#xff1a;2024-1-6至2024-1-7 会议地点&#xff1a;电子科技大学 会议介绍&#xff1a; 为提升我国神经计算⻘年研究队伍的学术⽔平和国际影响⼒&#xff0c;研讨会主题涵盖&#xff1a;神经系统建模与模拟、脑机接⼝与类脑智能、…

面试宝典之spring框架常见面试题

F1、类的反射机制有啥用&#xff1f; &#xff08;1&#xff09;增加程序的灵活性&#xff0c;可扩展性&#xff0c;动态创建对象。 &#xff08;2&#xff09;框架必备&#xff0c;任何框架的封装都要用反射。&#xff08;框架的灵魂&#xff09; F2、获取Class对象的三种方…

继承和多态的详解

文章目录 1. 继承1.1 继承的概念1.3 继承的语法1.3 父类成员访问1.3.1 子类中访问父类的成员变量1.3.2 子类中访问父类的成员方法 1.4 子类构造方法 2.super关键字2.1 super关键字的概念2.2 super和this的区别 3. 在继承中访问限定符的可见性4. 继承方式的分类5. 多态5.1 多态的…

Maven 基础总结篇

Maven 基础总结篇 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构&#xff1a;用于解决不同IDE&#xff08;例如eclipse与IDEA&#xff09;不同的项目结构的问题 提供了一套标准化的构建流程&#xff08;编译&…

数据结构与算法教程,数据结构C语言版教程!(第三部分、栈(Stack)和队列(Queue)详解)二

第三部分、栈(Stack)和队列(Queue)详解 栈和队列&#xff0c;严格意义上来说&#xff0c;也属于线性表&#xff0c;因为它们也都用于存储逻辑关系为 "一对一" 的数据&#xff0c;但由于它们比较特殊&#xff0c;因此将其单独作为一章&#xff0c;做重点讲解。 使用栈…

高效构建Java应用:Maven入门和进阶(三)

高效构建Java应用&#xff1a;Maven入门和进阶&#xff08;三&#xff09; 三. Maven的核心功能和构建管理3.1 依赖管理和配置3.2 依赖传递和冲突3.3 依赖导入失败场景和解决方案3.4 扩展构建管理和插件配置 三. Maven的核心功能和构建管理 3.1 依赖管理和配置 Maven 依赖管理…

Python基础知识:整理7 字典的定义及其相关操作

1 字典的定义 # 1. 字典的定义 # 定义字典的字面量 # {key: value, key: value, ......, key: value}# 定义字典变量 # my_dict {key: value, key: value, ......, key: value}# 定义空字典 # my_dict {} # my_dict dict()定义重复Key的字典 my_dict1 {"张三": …

随机输一次

大家应该都会玩“锤子剪刀布”的游戏&#xff1a;两人同时给出手势&#xff0c;胜负规则如图所示&#xff1a; 现要求你编写一个控制赢面的程序&#xff0c;根据对方的出招&#xff0c;给出对应的赢招。但是&#xff01;为了不让对方意识到你在控制结果&#xff0c;你需要隔 K …