C++ 多态详解:从概念本质、语法规则到底层实现,结合实战代码的全方位指南

C++ 多态详解:从概念本质、语法规则到底层实现,结合实战代码的全方位指南
在这里插入图片描述

🔥草莓熊Lotso:个人主页
❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:

在这里插入图片描述

文章目录


前言:

多态是 C++ 面向对象 三大特性(封装、继承、多态) 的核心,它让 “同一行为作用于不同对象产生不同结果” 成为可能。本文将从多态的基础概念切入,逐步拆解多态的构成条件、虚函数重写规则及关键细节,帮你彻底掌握运行时多态的实现逻辑。

一. 多态的概念:从“多种形态说起”

1.1 多态的概念解析

多态通俗来说就是 “多种形态” ,在C++中分为两类:

  • 编译时多态(静态多态):通过函数模板,重载来实现,编译阶段确定调用的函数(如add(1,2)add(1,2,3,4)调用的不同的函数)
  • 运行时多态(动态多态):本篇博客核心讲解的地方,通过”基类指针/引用 + 虚函数重写“,运行阶段根据指向的对象的类型确定调用的函数

本文代码示例所需头文件

#include<iostream>usingnamespace std;

代码仓库多态 - Gitee.com

1.2 生活中的多态示例

最经典的场景就是“买票行为”

  • 普通人买票 -> 全价
  • 学生买票 -> 半价
在这里插入图片描述

用样是“买票”,不同对象执行不同逻辑,这就是多态的本质。我们接下来会在代码中通过继承和多态来实现“调用同一函数”产生不同结果。


二. 多态的构成条件和核心语法

多态是一个 继承关系 下的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象优惠买票(当然再来个军人对象继承Person的话,这个对象去调用也会由不同的结果)

要实现多态,除了要有继承关系,还必须满足下面这两个强制条件,缺一不可

  • 必须是基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,并且派生类对基类的虚函数完成了 “重写” (覆盖)。

说明:要实现多态的效果,第一必须是基类的指针或者引用,因为只有基类的指针或引用才能既指向基类对象又指向派生类对象;第二派生类必须对基类的虚函数完成重写/覆盖,重写了,基类和派生类之间才能有不同的函数,多态的不形态效果才能达到。

在这里插入图片描述

2.1 条件 1:虚函数的定义

类成员函数前加virtual关键字,该函数即为虚函数(非成员函数和静态成员函数不能加virtual)。虚函数的作用是 “标记” 该函数需要参与多态,让编译器为其生成动态绑定逻辑。

// 基类:PersonclassPerson{public:// 虚函数:标记为需要参与多态virtualvoidBuyTicket(){ cout <<"买票-全价"<< endl;}};

2.2 条件 2:虚函数的重写(覆盖)

派生类中定义一个 “与基类虚函数完全一致” 的函数,即为重写。这里 “完全一致” 指:

  • 函数名相同;
  • 参数列表(参数类型、个数、顺序)相同(缺省参数可以不管);
  • 返回值类型相同(协变除外,下文讲解)。

代码示例(注意看注释)

// 基类:PersonclassPerson{public:// 虚函数:标记为需要参与多态virtualvoidBuyTicket(){ cout <<"买票-全价"<< endl;}};// 派生类:Student(继承Person)classStudent:publicPerson{public:// 重写基类虚函数:函数名、参数、返回值完全一致// 派生类中virtual也可以省略virtualvoidBuyTicket(){ cout <<"买票-打折"<< endl;}};// 派生类:Soldier(继承Person)classSoldier:publicPerson{public:// 重写基类虚函数// 派生类中virtual也可以省略virtualvoidBuyTicket(){ cout <<"买票-优先"<< endl;}};// 关键:用基类指针调用虚函数(满足多态条件1)// 这里也可以用基类引用voidFunc(Person* ptr){// 运行时根据ptr指向的对象类型,调用对应类的BuyTicket// Person* ptr ptr->BuyTicket();// 如果是基类引用(Person& ptr)//ptr.BuyTicket();}intmain(){ Person ps;// 基类对象 Student st;// 派生类对象(学生) Soldier sr;// 派生类对象(军人)Func(&ps);// 指向基类对象 → 调用Person::BuyTicket → 输出“买票-全价”Func(&st);// 指向学生对象 → 调用Student::BuyTicket → 输出“买票-打折”Func(&sr);// 指向军人对象 → 调用Soldier::BuyTicket → 输出“买票-优先”return0;}
在这里插入图片描述

注意:派生类重写时,即使不加virtual,也能构成重写(因为基类虚函数的 “虚属性” 会被继承),但不建议这么写,可读性差且易出错。
坑点(下面的笔试题会有体现):在 C++ 中,虚函数重写时若基类和派生类的虚函数都指定了缺省参数,调用时的缺省值只由 “基类的函数声明” 决定,与派生类的重写实现无关。

2.3 多态场景的一个笔试选择题(重要):

以下程序输出的结果是什么(B
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

classA{public:virtualvoidfunc(int val =1){ std::cout <<"A->"<< val << std::endl;}virtualvoidtest(){func();}};classB:publicA{public:voidfunc(int val =0){ std::cout <<"B->"<< val << std::endl;}};intmain(int argc,char* argv[]){ B* p =new B; p->test();return0;}

图解如下

在这里插入图片描述

改编扩展:以下程序输出的结果是什么(D
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

classA{public:virtualvoidfunc(int val =1){ std::cout <<"A->"<< val << std::endl;}virtualvoidtest(){func();}};classB:publicA{public:voidfunc(int val =0){ std::cout <<"B->"<< val << std::endl;}};intmain(int argc,char* argv[]){ B* q =new B; q->func();return0;}

图解如下

在这里插入图片描述

三、虚函数重写的特殊情况

虚函数重写并非只有 “完全一致” 一种情况,还有两种特殊场景需要注意:协变和析构函数重写,这也是面试高频考点

3.1 协变(了解)

派生类重写基类虚函数时,返回值类型可以不同,但必须满足:

  • 基类虚函数返回 “基类对象的指针 / 引用”;
  • 派生类虚函数返回 “派生类对象的指针 / 引用”。

这种情况称为 “协变”,实际开发中使用较少,了解即可。

代码示例(注意看注释)

#include<iostream>usingnamespace std;// 基类AclassA{};// 派生类B(继承A)classB:publicA{};// 基类PersonclassPerson{public:// 虚函数:返回基类A的指针virtual A*BuyTicket(){ cout <<"买票-全价"<< endl;returnnullptr;}};// 派生类StudentclassStudent:publicPerson{public:// 重写:返回派生类B的指针(协变)virtual B*BuyTicket(){ cout <<"买票-打折"<< endl;returnnullptr;}};voidFunc(Person* ptr){ ptr->BuyTicket();// 多态调用依然生效}intmain(){ Person ps; Student st;Func(&ps);// 输出“买票-全价”Func(&st);// 输出“买票-打折”return0;}

3.2 析构函数的重写(重点)

基类的析构函数为虚函数:基类析构函数加virtual后,派生类析构函数无论是否加virtual,都构成重写。这是因为编译器会将所有析构函数的名称统一处理为destructor,看似名称不同,实则一致。

注意:这个在面试题中经常考到,问基类中的析构函数建不建议写成虚函数?大家可以结合下面的代码示例和为什么去进行回答,这样才能讲清楚

为什么需要析构函数构成重写?
如果基类析构函数不是虚函数,用基类指针指向派生类对象并delete时,只会调用基类析构函数,导致派生类中动态申请的资源无法释放,引发内存泄漏

代码示例(注意看注释,其中额外测试部分是补充了解的和这里的重点不同)

classA{public:// 基类析构函数加virtual,支持重写virtual~A(){ cout <<"~A()"<< endl;}};classB:publicA{public:// 派生类析构函数:自动构成重写(加不加virtual都可以)~B(){ cout <<"~B()->delete:"<< _p << endl;delete _p;// 释放派生类动态申请的资源}protected:int* _p =newint[10];// 派生类动态申请的数组};voidtest(){ cout <<"--------额外测试结果--------"<< endl;//额外测试,这个是正常场景,加不加都行//只是为了让大家了解一下这个析构顺序//析构顺序:~B(),~A(),~A()//其中第一个~A()是因为子类B析构完后调用基类的(先子后父),后面一个是a对象析构 A a; B b;}// 基类只要保障了析构函数是虚函数,下面场景就不会存在内存泄漏intmain(){// 基类指针指向派生类对象 A* ptr1 =new B;delete ptr1;// 多态调用:先调用~B(),再先子后父自动调用~A(),无内存泄漏// 基类指针指向基类对象 A* ptr2 =new A;delete ptr2;// 调用~A()test();return0;}
在这里插入图片描述


主要测试版块:如果基类析构不加 virtualdelete ptr1只会调用~A(),B类中_p指向的数组未释放,导致内存泄漏。额外测试那里加不加都行。


四. C++11:override 与 final 关键字

虚函数重写对语法要求严格(如函数名写错、参数类型不匹配),这些错误编译时不会报错,只会在运行时出现非预期结果。C++11 提供overridefinal两个关键字,帮我们在编译阶段检测错误。

4.1 override:检测是否重写

在派生类虚函数后加override,编译器会检查该函数是否真的重写了基类虚函数。若未重写(如函数名错、参数错),直接编译报错。
代码示例(注意看注释)

classCar{public:// 基类虚函数:Drive(注意拼写是Drive,不是Dirve)virtualvoidDrive(){ cout <<"Car-行驶"<< endl;}};classBenz:publicCar{public:// 错误示例:函数名写成Dirve,加override后编译报错// virtual void Dirve() override { cout << "Benz-舒适" << endl; }// 正确示例:函数名正确,override检测通过virtualvoidDrive() override { cout <<"Benz-舒适"<< endl;}};intmain(){ Car* p =new Benz; p->Drive();// 多态调用:输出“Benz-舒适”return0;}
在这里插入图片描述

4.2 final:禁止重写

在基类虚函数后加final,表示该虚函数不允许任何派生类重写。若派生类强行重写,编译报错。
代码示例(注意看注释)

classCar{public:// 基类虚函数加final:禁止派生类重写virtualvoidDrive() final { cout <<"Car-行驶"<< endl;}};classBenz:publicCar{public:// 错误:Drive()被final修饰,无法重写,编译报错// virtual void Drive() override { cout << "Benz-舒适" << endl; }};intmain(){return0;}

五. 易混淆概念:重载、重写、隐藏的对比(常考)

多态相关的三个概念(重载、重写(覆盖)、隐藏(重定义)) 极易混淆,我们通过下面的图片,代码示例和表格来加强一下对它们的区分:

在这里插入图片描述

代码示例(借鉴-注意看注释)

classBase{public:// 1. 重载:同一作用域,函数名相同,参数不同voidfunc(int a){ cout <<"Base::func(int)"<< endl;}voidfunc(double b){ cout <<"Base::func(double)"<< endl;}// 虚函数:用于重写virtualvoidshow(){ cout <<"Base::show()"<< endl;}};classDerive:publicBase{public:// 2. 重写:基类与派生类,虚函数+函数名/参数/返回值相同virtualvoidshow() override { cout <<"Derive::show()"<< endl;}// 3. 隐藏:基类与派生类,函数名相同但不构成重写voidfunc(int a,int b){ cout <<"Derive::func(int,int)"<< endl;}};intmain(){ Derive d; d.func(1,2);// 调用Derive::func(隐藏基类func)// d.func(3); // 编译报错:基类func(int)被隐藏,需显式调用Base::func(3) Base* p =&d; p->show();// 多态调用:Derive::show(重写)return0;}
特性重载(Overload)重写(Override)隐藏(Hide)
作用域同一类(同一作用域)基类与派生类(不同作用域)基类与派生类(不同作用域)
函数名必须相同必须相同必须相同
参数列表必须不同(类型/个数/顺序)必须相同可相同可不同
返回值类型无要求必须相同(协变除外)无要求
虚函数要求必须都是虚函数
核心场景同一类中同名函数的不同实现多态的核心,动态绑定派生类屏蔽基类同名成员(非重写)
底层机制编译期静态绑定,通过参数列表区分函数运行期动态绑定,依赖虚函数表编译期静态绑定,通过作用域区分
示例class A { void func(int); void func(double); }class A { virtual void func(); }; class B : public A { void func() override; }class A { void func(); }; class B : public A { void func(int); }
注意事项仅在同一类中生效,派生类中若与基类函数同名且参数不同,会隐藏基类函数重写时函数签名(函数名+参数+返回值)必须严格一致,析构函数重写有特殊性若派生类函数与基类虚函数同名但参数不同,会隐藏基类虚函数,导致多态失效

结尾:

🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:C++ 多态的本质,是用 “统一接口” 包裹 “差异化实现”,让代码既能保持调用逻辑的一致性,又能适配不同对象的特性 —— 从 “买票” 的场景差异,到析构函数的资源安全释放,多态始终在平衡 “通用性” 与 “灵活性”。

✨把这些内容吃透超牛的!放松下吧✨ʕ˘ᴥ˘ʔづきらど

Read more

文墨共鸣多场景:同时支持短文本比对(标题)、中长文本(段落)、长文本(章节)

文墨共鸣多场景:同时支持短文本比对(标题)、中长文本(段落)、长文本(章节) "夫文心者,言为心声,义为神合。" 文墨共鸣将深度学习算法与传统水墨美学完美融合,基于StructBERT技术,精准识别文字间的微妙关联,无论是标题、段落还是章节,都能准确判断是"异曲同工"还是"云泥之别"。 1. 项目概览:当AI遇见水墨艺术 文墨共鸣是一个创新的语义相似度分析系统,它将先进的自然语言处理技术与典雅的中国传统美学相结合。不同于传统的技术工具,文墨共鸣在准确分析文本相似度的同时,为用户带来沉浸式的文化体验。 这个系统的核心价值在于其多场景适配能力:从几个字的标题比对,到数百字的中长段落分析,再到数千字的长章节对比,都能提供精准的语义相似度判断。无论是学术研究、内容创作还是日常办公,都能找到适用的场景。 2. 核心功能:全场景文本比对 2.1 短文本比对:精准捕捉标题精髓 短文本比对专门处理标题、标语、

By Ne0inhk
百度发布文心一言5.0预览版大模型:多模态能力全面超越GPT-5?

百度发布文心一言5.0预览版大模型:多模态能力全面超越GPT-5?

2025年11月13日,在百度世界大会上,中国搜索巨头百度正式发布了其最新一代基础大模型——文心一言5.0预览版(ERNIE 5.0 Preview)。这款模型不仅在多项关键基准测试中宣称“击败”OpenAI的GPT-5和谷歌的Gemini 2.5 Pro,更标志着百度在全球企业级AI市场的雄心进一步升级。 文心一言5.0预览版 与此前开源的ERINE-4.5-VL-28B-A3B-Thinking不同,文心一言5.0预览版(ERNIE 5.0 Preview)是一款闭源专有模型,仅通过百度“文心一言”官网及面向企业客户的千帆平台API提供服务。 该模型被定位为“原生全模态”(natively omni-modal)基础模型,能够同步处理并生成文本、图像、音频与视频内容,而非依赖传统的“后融合”方式拼接不同模态。百度强调,这种一体化架构使其在复杂任务中具备更强的上下文理解与跨模态推理能力。 此外,百度还同步推出了一个专门优化文本密集型任务的变体——文心一言5.0预览版1022(ERNIE 5.0

By Ne0inhk

如何在低显存GPU上流畅运行AI绘画:ComfyUI GGUF量化完全指南

如何在低显存GPU上流畅运行AI绘画:ComfyUI GGUF量化完全指南 【免费下载链接】ComfyUI-GGUFGGUF Quantization support for native ComfyUI models 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-GGUF 还在为AI绘画时GPU显存不足而烦恼吗?ComfyUI GGUF量化技术为你带来全新的解决方案,让低性能显卡也能流畅运行大型AI模型。 问题:显存瓶颈如何突破? 大多数AI绘画爱好者都遇到过这样的困境:想要运行高质量的扩散模型,却发现自己的显卡显存远远不够。传统的UNET模型量化效果不佳,而GGUF格式的出现改变了这一局面。 解决方案:GGUF量化技术 GGUF是一种高效的模型文件格式,专门为量化优化设计。与常规的卷积神经网络不同,基于transformer/DiT架构的模型(如flux系列)在量化后性能损失极小,这为低显存GPU用户打开了新的大门。 通过ComfyUI-GGUF项目,你可以: * 将模型文件大小显著压缩 * 在低至4位/权重

By Ne0inhk
我的第一部AIGC电影《编钟》制作幕后

我的第一部AIGC电影《编钟》制作幕后

当今时代,AI已经能制作一些高质量的电影片段。 我在前文就介绍过AIGC创作的一个标准工作流,并计划在两个月内完成一部5分钟的AI微电影。 如今,Seedance2.0这款模型彻底改变了工作流程,并将原定计划2个月的时间,压缩成了两天。 目前,该片参与了B站最近举办的视频创作大赛,参加的是三体赛道。 视频链接:https://www.bilibili.com/video/BV11acizcEjR 故事梗概 《编钟》讲述在二向箔打击地球前最后24小时,月球转运站工程师陈末得知航道封锁、末日将至,毅然驾驶一架濒临报废的穿梭机逆流返航,只为兑现对盲女小雅的承诺——带她去听两千年前的编钟之声。 城市在恐慌与崩塌中走向终结,空间开始二维化,高楼化作平面残影;陈末穿越混乱,将女儿带入空无一人的博物馆,在老守夜人的引领下敲响曾侯乙编钟。 浑厚钟声穿透濒毁的天地,小雅在声音中“看见”金色的高音、深蓝的低音与绿色的中音,完成了关于“声音颜色”的愿望。 当最后一声钟鸣与二维浪潮同时降临,地球在二向箔的打击下,彻底压缩成二维平面,人类文明的火种也随声音为载体,向外太空传播。 制作复盘

By Ne0inhk