c++复习::深入理解多态
一、多态的概念直白来说就是去完成某个事情不同的对象会呈现出不同的状态价值1、举个例子比如说购买高铁票这个行为如果是普通人就是原价购买如果是学生的话就是半价购买如果是军人的话可以优先走绿色通道购买……总的来说在生活中去做一件事情不同的人去做会有不同的反馈这就是多态。二、多态的定义和实现2.1 构成多态的条件首先多态现象的产生是在继承的基础上产生的。多态是在不同继承关系的类对象去调用同一函数产生了不同的行为。比如Student继承了Person。Person对象买票全价Student对象买票半价。#includeiostream using namespace std; class Person { public: virtual void Buyticket() { cout 买票全价 endl; } }; class Student:public Person { public: virtual void Buyticket() { cout 买票半价 endl; } }; void func(Personptr) { ptr.Buyticket(); } void test1() { Student s1; Person p1; func(s1); func(p1); } int main() { test1(); return 0; }想要构成多态基类的成员函数前面必须加入virtual而派生类可加可不加但为了增强可读性这里还是建议加上。写法上需要满足的条件公有继承基类函数加virtual派生类完成重写 override使用【基类指针 / 基类引用】接收派生对象构成多态需要以下两个条件重点1对父类虚函数的重写-三同函数名、参数、返回值2必须是父类的指针或者引用去调用我们来画图分析一下构成多态与不构成多态的情况#includeiostream using namespace std; class Person { public: virtual void Buyticket() { cout 买票全价 endl; } }; class Student:public Person { public: virtual void Buyticket() { cout 买票半价 endl; } }; void func1(Person*ptr) { ptr-Buyticket(); } void func2(Person ptr) { ptr.Buyticket(); } //构成多态 void test1() { Student s1; Person p1; cout 构成多态: endl; func1(s1); func1(p1); cout --------------------- endl; cout 不构成多态: endl; func2(s1); func2(p1); } int main() { test1(); return 0; }根据运行结果可以清楚的看出func1()支持多态而func2()却不支持多态。原因我们首先得知道虚函数指针(vfptr)指向了虚表存在于只读数据段全局唯一每一个类一份虚表而不是每个对象一份而每个vfptr存在于带有虚函数的基类子对象的头部也就是说每个基类子对象里面存放着虚指针。当s1被构造出来时Person子对象头部自带vfptr而这个vfptr自动指向Student的虚函数表然后当执行func时指针ptr指向了对象s1的起始地址也就是Perosn部分但是呢Student对象是完整保留的然后我们执行BuyTicket()时通过指针 p 拿到地址 APerson 子对象首地址从地址 A 处取出保存的vfptr虚函数指针vfptr 指向【Student 的虚函数表】在虚函数表里根据函数偏移找到Student::BuyTicket()函数地址跳转调用 Student::BuyTicket。而当你进行值传递时会调用拷贝构造形成一个新的Person对象可以理解为func2(Person ptr)内部这个ptr就是一个纯Person对象调用BuyTicket()当然是调用自己的了换句话理解新建出来的这个 Person 对象构造时把自身 vfptr 设置为Person 的虚函数表核心分水岭一句话区分Person* pp 只是地址引用操作原来完整的 Student 对象vfptr 保持 Student 的虚表Person p创建全新独立基类对象vfptr 被重置为基类自己的虚表和原来 Student 切断联系。一张图理解好思考你可能会有这样的疑惑-我直接用在函数体里面用if……else不也可以达到这样的效果吗为什么非得用多态来完成呢——答有些场景下必须得用多态才能解决比如父类的指针或者引用调用析构函数2.2 虚函数的重写虚函数即被virtual修饰的类成员函数称为虚函数虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数。两个例外1.协变返回值可以不同-但是必须是父子关系的指针或者引用其实不一定是自己的父子类其他的父子类也行class Person { public: virtual Person* f() { return this; } }; class Student : public Person { public: virtual Student* f() { return this; } }; int main() { return 0; }2.子类的virtual可以省略因为虚函数的重写本身就是接口继承我把除函数体以外的全部部分都可以继承下来然后再去重写继承父类的这个函数的实现 这样只要父类写了virtual就可以了。class A { public: virtual void func(int val 1) { std::cout A- val std::endl; } virtual void test() { func(); } }; class B : public A { public: void func(int val 0) { std::cout B- val std::endl; } }; int main(int argc, char* argv[]) { B* p new B; p-test(); return 0; }我们刚才分析过了虚函数的重写是继承函数体以外的所有内容包括返回值和形参当你创造出B的对象时p-test()时因为B中没有test所以用了从A中继承下来的test这里满足父类的指针调用满足多态然后再调用func又因为A中的func和B中的func构成了函数重写然后this会根据你动态绑定的对象查询虚表调用B::func(int)在编译期发生替换将形参改成静态绑定的A中func函数的形参也就是int val1最后执行B中的函数体coutB-valendl;这里有个重点动态绑定和静态绑定:this 静态类型A* const由函数定义所在类决定test定义在 class A 中✅this 动态类型指向堆上的 B 对象运行时真实对象类型1. 静态绑定又称为前期绑定(早绑定)在程序编译期间确定了程序的行为也称为静态多态编译时比如函数重载2. 动态绑定又称后期绑定(晚绑定)是在程序运行期间根据具体拿到的类型确定程序的具体行为调用具体的函数也称为动态多态运行时。函数版本选择时我们依靠的是动态绑定默认形参我们看的是静态绑定。析构函数的重写如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。虽然函数名不相同看起来违背了重写的规则其实不然这里可以理解为编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor。2.3 重载、覆盖和隐藏的对比三、抽象类3.1抽象类的介绍在虚函数后面加上0就形成了纯虚函数含有纯虚函数的类我们称为抽象类抽象类不能实例化出对象派生类继承后也不能实例化出对象必须对虚函数重写才能实例化出对象纯虚函数规定了虚函数必须重写更能体现接口的继承1.抽象类不能实例化出对象2.子类必须重新父类的虚函数不重写的话自己也无法实例化总的来说纯虚函数强制子类必须重写虚函数相当于是一种强制性的要求 如果不重写的话代价就是自己也和抽象类父类一样无法实例化出对象3.2总结实现继承与接口继承普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。所以如果不实现多态不要把函数定义成虚函数充分说明了多态的意义就是为了重写虚函数而生的四、多态的底层原理4.1虚函数表首先我们先用一个例子来对底层进行一波剖析计算出size(base)的大小根据内存对齐规则很多人计算出的base是8但是这里是12(x86环境下)为什么呢因为类里面只要出现了虚函数就一定存在虚函数表指针指向虚表。_vfptr就是一个虚函数表指针一般这个_vfptr都放在所有对象的最前面虚函数表指针指向虚表虚表中存放着虚函数的地址。我们来分析一行代码class Person { public: virtual void BuyTicket() { cout 买票-全价 endl; } virtual void Func() { cout 买票-全价 endl; } int _a 0; }; class Student : public Person { public: virtual void BuyTicket() { cout 买票-半价 endl; } int _b 1; }; void Func(Person* p) { p-BuyTicket(); } void test4() { Person* p1 new Person; Person* p2 new Student; } int main() { test4(); return 0; }我们来画图分析:从上的分析我们可以总结出1、虚函数表简称虚表本质上是一个虚函数指针数组2、子类在创建对象的时候会拷贝父类的虚函数表然后如果没有发生重写比如Func 那虚函数表存的就是父类的虚函数如果发生了重写比如BuyTicket()那么子类首先会继承父类的接口然后重写父类的实现然后用这个虚函数的地址覆盖掉原先拷贝父类虚函数的位置。3、通过对2的分析我们可以知道1重写是语法层的概念如果发生了多态那么子类会继承父类的接口然后重写父类的实现。2覆盖是原理层的概念当子类重写了父类的实现后会将虚表中对应的该函数的地址更新成新的虚函数地址。4.2多态的原理1在没有发生多态的时候相当于一个普通函数的调用是在编译时就确定了的。2如果发生了多态那么编译器只能通过找到对应的虚函数表的位置然后虚函数表里面存的函数是什么就调用什么。这充分说明了一个道理就是多态并不是在编译时就确定的编译器只是知道自己需要调用的函数在什么地方但是具体这个函数是什么他并不知道只有等到汇编代码转成二进制代码后执行了那么才会知道调用的是什么我们来看这段代码class 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; } virtual void Func4() { cout Derive::Func4() endl; } private: int _d 2; }; int main() { Base b; Derive d; }我们分析得出d应该存有三个虚函数b应该存有两个虚函数单一继承全程只有一个虚指针被派生类复用、指向Derive 专属的虚表。在vs的调试中Derive的虚函数表指针嵌套在了base当中但是这个虚函数表指针早就改变了指向指向了Derive的虚表。4.3 多态的思考1、虚表是什么阶段生成的——编译阶段一个类是否存在虚表关键就在于是否有虚函数而是否有虚函数取决于是否有virtual关键字所以是在编译阶段生成。2、对象中的虚表指针是什么时候初始化的——初始化列表阶段虚表指针的初始化还要早于构造函数虚表指针是在初始化列表阶段进行的。3、虚表是存在哪里的——代码段常量区因为我们知道虚函数是存在代码段常量区而虚表的地址和虚函数的地址其实挺接近的。但是这样其实不够严谨所以我们自己来写一个代码判断虚表具体是存在哪个区域。class Derive : public Base { public: // 重写基类的func1 virtual void func1() { cout Derive::func1 endl; } virtual void func3() { cout Derive::func1 endl; } void func4() { cout Derive::func4 endl; } protected: int b 2; }; int main() { int i 0; static int j 1; int* p1 new int; const char* p2 xxxxxxxx; printf(栈:%p\n, i); printf(静态区:%p\n, j); printf(堆:%p\n, p1); printf(常量区:%p\n, p2); Base b; Derive d; Base* p3 b; Derive* p4 d; printf(Person虚表地址:%p\n, *(int*)p3); printf(Student虚表地址:%p\n, *(int*)p4); printf(虚函数地址:%p\n, Base::func1); printf(普通函数地址:%p\n, Base::func5); return 0; }我们可以清楚的看到两个虚表的地址隔得挺近都在常量区而虚函数地址也在常量区两者却隔得很远。存在常量区的原因1、虚函数表的内容在编译的时候就确定了后面不需要修改因此适合放在只读区2、节省内存多个同类对象共享同一个虚函数表只需要在内存中存一份。

相关新闻

Fortify SCA代码审计工具:从安装配置到CI/CD集成的完整实战指南

Fortify SCA代码审计工具:从安装配置到CI/CD集成的完整实战指南

1. 项目概述:为什么我们需要专业的代码审计工具?在软件开发的漫长周期里,代码安全一直是个让人头疼又无法回避的问题。我见过太多项目,前期功能迭代飞快,后期却因为层出不穷的安全漏洞而疲于奔命,甚至导致数…

2026/10/19 11:22:12 阅读更多 →
STM32H7硬件IIC驱动EEPROM:从原理到实战避坑指南

STM32H7硬件IIC驱动EEPROM:从原理到实战避坑指南

1. 项目概述:为什么STM32H7的硬件IIC读写EEPROM值得深究? 如果你正在用STM32H7系列做项目,大概率绕不开一个经典需求:通过IIC总线读写外部EEPROM。这听起来像是单片机开发的“Hello World”,但实际操作过的人都知道&am…

2026/10/20 6:22:18 阅读更多 →
从Claude Code源码泄露看AI工程安全:Source Map配置与构建部署防御

从Claude Code源码泄露看AI工程安全:Source Map配置与构建部署防御

1. 项目概述:从一次“意外”看AI工程的门槛与细节最近,AI编程助手领域发生了一件让所有开发者都捏了把汗的事——Anthropic旗下的Claude Code,其超过51万行的TypeScript源码,因为一个.map文件的配置疏忽,在构建产物中被…

2026/10/18 12:09:35 阅读更多 →

最新新闻

STM32F1实战全解析:选型、硬件底子、外设调试与排错思路

STM32F1实战全解析:选型、硬件底子、外设调试与排错思路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:20:57 阅读更多 →
第九章:了解特殊场景下的redis

第九章:了解特殊场景下的redis

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:20:57 阅读更多 →
UFS 3.1协议学习路径:从第11章切入掌握UPIU与命令队列

UFS 3.1协议学习路径:从第11章切入掌握UPIU与命令队列

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:20:57 阅读更多 →
传统图像处理+YOLO:提升目标检测精度的实战指南

传统图像处理+YOLO:提升目标检测精度的实战指南

1. 为什么又要回头用传统方法先说个实际场景。去年有一个做工厂质检的朋友来找我,说他们产线上用了YOLO之后,表面缺陷的漏检率始终压不下来,尤其是那种特别细的划痕,在强光和反光条件下时有时无,模型自己也很矛盾。后来…

2026/10/21 1:20:57 阅读更多 →
DevExpress Winform登录跳转主窗体:ApplicationContext生命周期管理与皮肤统一实战

DevExpress Winform登录跳转主窗体:ApplicationContext生命周期管理与皮肤统一实战

1. 登录跳转主窗体的整体设计思路做过Winform项目的人都有一个共识:登录窗体虽然逻辑不复杂,但它是整个应用的门面,也是新手最容易翻车的地方。我见过太多项目把登录逻辑直接塞进主窗体的构造函数里,结果调试的时候主窗体一闪而过…

2026/10/21 1:20:57 阅读更多 →
5分钟上手的WorkTool企业微信自动化机器人:安卓无障碍方案指南

5分钟上手的WorkTool企业微信自动化机器人:安卓无障碍方案指南

5分钟上手的WorkTool企业微信自动化机器人:安卓无障碍方案指南 【免费下载链接】worktool 一款安全稳定的Android无障碍服务工具,支持控制企微/微信来运行的无人值守群管理企业微信机器人 项目地址: https://gitcode.com/GitHub_Trending/wo/worktool …

2026/10/21 1:19:57 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →