C++?多态!!!

article/2025/7/3 9:02:56

一、引言

        众所周知,C++有三大特性,它们分别是封装、继承和多态,在之前的文章中已经详细介绍过封装和继承了,今天我们将一起学习多态相关的知识,如果还想了解封装、继承相关的知识,可以跳转到以下链接:
       

        1、封装:C++?类和对象(上)!!!-CSDN博客

        2、继承:C++?继承!!!-CSDN博客

二、多态的概念

        1、概念

        通俗来讲,多态表示多种状态,即就是说当面对不同类型、不同特点的对象时,处理一个问题时采用不同的方式从而产生不同的效果,这就是多态

        2、分类

        事实上,多态细分之下有两种,它们分别是静态多态和动态多态,我们常说的多态事实上代指动态多态,也就是我们今天将要主要讨论的内容,在详细了解了多态的相关知识之后我们将再来理解这两个概念

        3、从实际的角度认识多态

        上面我们介绍了多态的概念,这样我们可以按图索骥,大概举几个日常生活中常见的多态的实际应用:


                (1).打滴滴

                在打滴滴时,新人用户常常会享受较大的优惠力度,小编记得在我第一次打滴滴时,价格优惠到了4元,那天的路程还挺远的,如果放在今天可能会在十元往上,这里就用到了多态的相关知识(猜测),当一个新人用户和一个老用户同样的调用"打车"接口时,却对应了不同的优惠力度,这正好对应了多态的概念

                (2).买票系统

                我们日程生活中会进行各种各样的买票操作,比如各个景点或者是买回家的车票,不难发现,常见的对象会被平台分为:普通身份、学生、军人等

                当这些对象同样调用买票接口时,普通身份会全家买票,学生是半价买票,军人常见的则是优先买票,很明显,不同的对象调用同一接口,产生了不同的效果,对应了多态的概念

        通过以上两个常见的概念,我们可以感受到多态的相关知识是存在在我们生活中的方方面面的

三、多态的定义及实现

        1、虚函数

        虚函数:即就是被virtual关键字修饰的函数:
        

class Person
{
public:virtual void buy_t(){cout << "全价购票" << endl;}
};

        2、虚函数的重写

        虚函数的重写:派生类中有一个函数跟基类的虚函数三同(即函数名、函数参数、函数返回值都相同)的函数,那么就称该派生类重写(覆盖)了基类的虚函数,例如:
        

class Person
{
public:virtual void buy_t(){cout << "全价购票" << endl;}
};
class Student : public Person
{
public:void buy_t(){cout << "半价购票" << endl;}
};

        以上的情况我们就说Student类重写了Person类中的buy_t函数

        但是需要注意的是,虚函数重写存在以下两个例外:
               

                (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对象调用析构函
数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main(){Person* p1 = new Person;Person* p2 = new Student;
delete p1;delete p2;}return 0;

                上面的代码中,p1、p2都是Person*的变量,随后调用delete对这两个动态申请的空间进行释放,事实上delete对于自定义类型会调用对应类的析构函数,此时就产生了一个问题:两个空间都会调用Person的析构函数,这是我们不想看到的,我们希望的是对于p1调用Person的析构函数,而对于p2则是调用Student的析构函数

                这时候我们可以认真的观察一下我们上面的需求,好像就是使用基类的指针来调用同一个函数,同时我们想让该调用动作对于不同的对象产生不同的效果,是的,这就是我们前面多态讨论过的需求,现在只有一个条件还没有满足,就是函数名并不相同,所以我们顺理成章的想到要让编译器对析构函数名进行特殊处理,这样在将基类的析构函数写为虚函数时,自然的就解决了上面的问题

        3、多态的构成条件

        多态是在继承关系中,不同的类对象调用同一函数,产生了不同的行为,比如Student继承了Person,这时候Person对象全价买票,Student对象半价买票,所以首先的,多态是存在在继承关系中的

        在继承关系中要构成多态还有两个条件:
                (1).必须通过基类的指针或者饮用调用函数

                (2).被调用的函数必须是虚函数,同时派生类对基类的虚函数进行重写

        下面是构成多态的一个完整例子:

        

#include <iostream>
using namespace std;
//多态
class Person
{
public:virtual void buy_t(){cout << "全价购票" << endl;}
};
class Student : public Person
{
public:void buy_t(){cout << "半价购票" << endl;}
};
void func(Person& rp)
{rp.buy_t();
}
int main()
{Person p;Student s;func(p);func(s);return 0;
}

        这一段代码的运行结果如下:
        

四、C++11中提供的两个相关的关键字:override和final

        经过上面的讲解,我们发现,C++中构成重写从而构成多态的过程时非常严格的,而在平常的代码工作中我们很容易会犯一些错误,比如:大小写的问题、字母顺序的问题,这些问题产生时是很难发现的,对于这些问题,只是没有构成重写,但并没有编译、链接的错误,不会报错,非常头疼,所以在C++11中我们提供了override和final两个关键字,它们两个可以帮助我们检查这一类问题

        1、final:该关键字有两个作用

                (1).修饰虚函数,被修饰的函数不能被重写:
                
class Person
{
public:virtual void buy_t  ()final//final修饰了该函数{cout << "全价购票" << endl;}
};
class Student : public Person
{
public:void buy_t()//这个位置会报错:无法重写“final”函数 "Person::buy_t"{cout << "半价购票" << endl;}
};
                (2).修饰一个类,被修饰的类不能被继承  

                

#include <iostream>
using namespace std;
//多态
class Person final//使用final修饰这个类
{
public:virtual void buy_t(){cout << "全价购票" << endl;}
};
class Student : public Person//这个位置会报错:不能将"final"类类型用作基类
{
public:void buy_t(){cout << "半价购票" << endl;}
};

        2、override:检查派生类函数是否重写了基类某个虚函数,如果没有就报错

        

class Person
{
public:virtual void buy_t(){cout << "全价购票" << endl;}
};
class Student : public Person
{
public:void buy_tx() override//override修饰该函数//该位置报错:使用override修饰的函数不能重写基类成员{cout << "半价购票" << endl;}
};

五、对比重载、重写(覆盖)、重定义(隐藏)

六、抽象类

        1、概念

        在虚函数的函数头之后加上=0,此时该函数被称为纯虚函数,包含纯虚函数的类叫做抽象类(也叫做接口类),抽象类不能实例化出对象。派生类继承之后也不能实例化出对象,只有重写了纯虚函数,派生类才能实例化出对象,纯虚函数规范了派生类必须重写,它更能体现出接口继承

        下面的代码体现出了这种接口继承的思想:
        

#include <iostream>
using namespace std;
//多态
class Person
{
public:virtual void buy_t() = 0;};
class Student : public Person
{
public:void buy_t(){cout << "半价购票" << endl;}
};
class Teacher :public Person
{
public:void buy_t(){cout << "十倍价钱购票" << endl;}};
void func(Person& rp)
{rp.buy_t();
}
int main()
{Teacher t;Student s;func(t);func(s);return 0;
}

        下面是以上代码的执行结果:
               

        2、接口继承和实现继承

         普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实 现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成 多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数

七、多态的原理

        1、虚函数表

                (1).引入
// 这里常考一道笔试题:sizeof(Base)是多少?
class Base{public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;};

                我们先通过打印的方式看一下这个问题的结果是多少?

                

                (2).解决问题

                可以看到,结果输出了8(这里要强调一下,小编实在x86的环境下输出的,环境或者平台改变可能会影响结果),这是为什么呢?或许含有虚函数的类对象进行了一些特殊处理?接下来我们通过调试的方法来看一下该类对象模型是怎样的:

                

                经过上面的调试窗口我们知道,原来在Base类中除了_b成员,还多一个__vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代 表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表,那么派生类中这个表放了些什么呢?我们接着往下分析

                为了符合多态的情景,我们先对上面的代码做出以下改造:
                

// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base// 2.Derive中重写Func1// 3.Base再增加一个虚函数Func2和一个普通函数Func3class Base{public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}private:int _b = 1;};class Derive : public Base{public:virtual void Func1(){cout << "Derive::Func1()" << endl;
}private:int _d = 2;};int main(){Base b;Derive d;return 0;}

                接下来我们一起观察这个加强版继承体系的类对象模型,从而说明派生类中的虚表有什么不同?

                

                可以观察到:继承之后的d对象模型中分为两个部分,分别是Base部分和自己的成员,而在Base部分中也有一个_vfptr指针,这意味着d不会生成自己的虚表指针,而是以继承的形式沿用了Base类的指针,而两个指针指向的位置是不同的,这就是说两个类的虚表是不同的,事实上的确是这样的,派生类会首先继承基类的虚表,然后对于重写过的函数将新的函数指针覆盖原本的函数指针,形成了属于自己的虚表

        2、多态的实现

        经过上面对于虚表指针和虚表的认识,我们大概也可以想到多态究竟是如何实现的

        事实上,多态的实现原理就是虚表指针存在在父子类中基类的部分,所以必须使用基类的指针或者引用调用(不能直接使用对象调用是因为对象的切片赋值会丢失信息,而指针和引用的切片赋值不会),同时通过虚表指针我们就可以找到虚表,父子类的虚表不同,找到的函数也就不同,这时候就实现了多态调用函数

        3、动态绑定与静态绑定

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

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

八、结语

        这就是本期有关多态的全部内容了,感谢大家的阅读,欢迎各位于晏、亦菲和我一起交流、学习、进步!!!                              、                
                

              

                              

                        

        


http://www.hkcw.cn/article/csMkpakswQ.shtml

相关文章

耿爽回应美方抹黑:完全不可接受 反对无端指责和政治操弄

中国常驻联合国副代表耿爽在安理会审议向乌克兰提供武器问题时发言指出,战场上武器数量不断增加只会加剧对抗、延长战火。自俄乌冲突爆发以来,中方一直呼吁冲突当事方尽快停火止战。遗憾的是,乌克兰危机仍在持续,平民伤亡人数不断增加,令人深感痛心和忧虑。在当前俄乌双方…

Assert failed in file queue.c, line 753

实在程序运行的时候出现的&#xff0c;根据提示找到相关的位置&#xff0c; 说明要操作的信号量还没被初始化&#xff08;注册&#xff09; &#xff0c;在抛信号量之前要使用sys_sem_new初始化一下。 如果出现这个问题&#xff0c;那么检查一下是不是忘了初始化。

著名物理学家汪承灏逝世 享年87岁贡献卓著

著名物理学家、中国科学院院士汪承灏研究员因病医治无效,于2025年5月29日在北京逝世,享年87岁。他曾担任政协北京市第十届委员会常务委员、中国科学院大学荣誉讲席教授及中国科学院声学研究所学术委员会原主任,并培养了众多博士生。汪承灏在功率超声、晶体声学、声表面波器件…

特朗普:特斯拉将在美国生产整车 必须在美国生产整车

5月30日,美国总统特朗普表示,美国汽车制造商必须在美国生产整车和所有零部件,而不是在国外生产。他提到,之前汽车制造商在加拿大、墨西哥、欧洲生产零部件,这让他感到困扰。特朗普强调,在接下来的一年里,这些汽车制造商需要在美国完成整车的生产。尽管特朗普有此要求,但…

马斯克:吃了儿子一拳,意外淤青引发猜测

5月30日,马斯克出现在白宫椭圆形办公室与美国总统特朗普的告别会上,眼角淤青引起外界猜测。马斯克解释说,这是他的儿子玩耍时打在他脸上的结果。他提到当时和儿子开玩笑,让儿子朝他脸上打一拳,没想到五岁的孩子也能造成这样的伤害。马斯克表示当时没觉得怎么样,但之后就出…

儿童节演出服穿完就退?商家出奇招 贴纸防退货

去年“六一”儿童节,商家投诉表演服被大量退货的事件频登热搜。今年临近“六一”,不少商家在社交平台上分享了防范技巧。从事童装生意10年的山东菏泽商家周女士就是其中之一。她发现退货的衣物损毁污染严重。为了避免再次遭受类似损失,周女士在每件儿童表演服上都贴上了醒目…

F1西班牙站一练:诺里斯全场最快 新秀表现亮眼

北京时间5月30日,F1西班牙大奖赛第一次练习赛结束。诺里斯以最快成绩领跑全场,维斯塔潘和汉密尔顿紧随其后。勒克莱尔、皮亚斯特里、劳森、贝尔曼、哈贾尔、角田裕毅和加斯利分别位列第四至第十名。拉塞尔排名第十一,安东内利排在第十八位,科拉平托垫底。今年新加入威廉姆斯…

单依纯《歌手》第三期第二 排名引发热议

5月30日晚,《歌手2025》第三期播出,本期迎来“袭榜战”。美国流行乐歌手查理普斯,昵称“断眉”,挑战成功,取代了单依纯的位置,白举纲被淘汰。排名依次为:格瑞丝金斯勒、单依纯、米奇盖顿、GAI周延、陈楚生、马嘉祺和白举纲。节目一开始,主持人何炅介绍了袭榜歌手查理普…

浏览器指纹科普 | Canvas 指纹是什么?

Canvas 是浏览器用来绘图的功能&#xff0c;常见于动画、图表等可视化内容。网站可以让你的浏览器绘制一张隐形图像&#xff0c;再读取这张图像的像素细节&#xff0c;生成一串唯一的“图像指纹”。 &#x1f50d; 它是怎么产生的&#xff1f; 虽然大家执行的绘图代码一样&…

曝苹果重启固态按键研发 全产品线探索触觉按钮方案

据博主 @刹那数码 爆料,苹果重启了内部代号为 bongo 的项目,该项目不仅涉及iPhone,还包括iPad、Apple Watch等全产品线。bongo 项目旨在探索触觉按钮方案,即去掉所有物理按钮,转而采用带有触觉反馈的固态按键。此前该博主提到,iPhone 固态按钮项目的生产成本不是问题,但…

RFID推动医行智能终审系统药物管理应用案例

一、项目背景 在医疗行业&#xff0c;药物管理的准确性和效率至关重要。传统的药物信息管理方式依赖人工记录和检索&#xff0c;容易出现错误且效率低下。为了提升药物管理水平&#xff0c;某大型医院引入医行智能终审系统&#xff0c;并结合广州晨控智能的RFID读卡器&#xf…

【文献速递】结合AI解析解毒三根汤对抗结直肠癌的生物活性成分

2025年4月21日&#xff0c;浙江省中医院阮善明教授团队在Phytomedicine&#xff08;IF&#xff1a;6.7&#xff09;上发表了题为“Bioactive components of Jiedu Sangen decoction against colorectal cancer: A novel and comprehensive research strategy for natural drug …

深入探究 MNIST 数据集 - Fastai 第三部分

在 fastai 第一部分中&#xff0c;我们学习了如何对 MNIST 数据集进行分类。在本教程中&#xff0c;我们将更深入地了解其底层原理。首先&#xff0c;我们将详细探索 MNIST 数据集。 数据探索 # 第一部分的代码 import torch import random from fastai.vision.all import *#…

5. 算法与分析 (2)

本节主要介绍算法时间复杂度的具体求法和空间复杂度 本文部分ppt、视频截图来自&#xff1a;[青岛大学-王卓老师的个人空间-王卓老师个人主页-哔哩哔哩视频] 1. 算法分析 1.1 分析算法时间复杂度的基本方法 定理1.1 即忽略所有低次幂项和最高次幂系数&#xff0c;体现出增长…

RCU初步分析

RCU初步分析 背景知识RCU介绍名词定义RCU的基本执行过程基本过程基本思想示意图基本流程示例代码并发执行示意图 RCU特性简易RCU实现基于spinlock的实现基于计数器的实现基于线程变量的实现 Linux内核中经典RCU实现介绍 背景知识 随着硬件晶体管的尺寸越来越小,CPU的频率上限基…

多名中国公民被印度拘捕 中使馆提醒 边境风险需警惕

中国驻尼泊尔使馆近期发布消息,提醒旅尼中国公民避免前往尼印边境地区。尽管多次发出警告,仍有部分中国公民未听从劝告,执意前往该区域,导致多起被捕事件。尼泊尔和印度之间的边界开放,两国公民可凭身份证件自由往来,但外国公民不能免签证经尼泊尔进入印度。中国公民在尼…

解锁AI超级能力:30+款MCP服务器全景指南

MCP服务器是当前AI领域的热门话题&#xff0c;几乎每个人都渴望参与其中。简单来说&#xff0c;MCP&#xff08;模型上下文协议&#xff0c;Model Context Protocol&#xff09;服务器是一种REST API服务器&#xff0c;充当大型语言模型&#xff08;LLM&#xff09;与各种外部系…

【cpp-httplib】 安装与使用

cpp-httplib 1. 介绍2. 安装3. 类与接口3.1 httplib请求3.2 httplib响应3.3 httplib服务端3.4 httplib客户端 4. 使用4.1 服务端4.2 客户端 1. 介绍 C HTTP 库&#xff08;cpp-httplib&#xff09;是一个轻量级的 C HTTP 客户端/服务器库&#xff0c;它提供了简单的 API 来创建…

HPE推出全新分布式服务交换机及有线无线产品组合,全面赋能AI与高性能计算需求

HPE Aruba Networking将分布式服务交换机性能全面升级,实现能力翻倍 休斯顿-2025年5月29日-慧与科技(NYSE:HPE)日前宣布全面扩展HPE Aruba Networking有线及无线网络产品组合,并重磅推出全新HPE Aruba Networking CX 10K分布式服务交换机。该系列交换机搭载AMD Pensando可编程…

烟草工业数字化转型:科技领航,重塑传统产业新生态

在科技浪潮席卷各行业的当下&#xff0c;烟草工业这一传统产业也迎来了深刻变革。《烟草工业数字化转型&#xff1a;科技领航&#xff0c;重塑传统产业新生态》这一主题&#xff0c;精准揭示了数字化技术如何在具有独特生产工艺与严格监管要求的烟草工业中&#xff0c;发挥关键…