【C++】多态(下)

【C++】多态(下)
在这里插入图片描述

个人主页~

多态(上)~


多态

四、多态的原理

1、虚表的存储位置

classA{public:virtualvoidfunc1(){ cout <<"A::func1"<< endl;}virtualvoidfunc2(){ cout <<"A::func2"<< endl;}private:int _a;};voidfunc(){ cout <<"void func()"<< endl;}intmain(){ A a1; A a2;staticint a =0;int b =0;int* p1 =newint;constchar* p2 ="hello world";printf("静态区:%p\n",&a);printf("栈:%p\n",&b);printf("堆:%p\n", p1);printf("代码段:%p\n", p2);printf("虚表:%p\n",*((int*)&a1));printf("虚函数地址:%p\n",&A::func1);printf("普通函数地址:%p\n", func);return0;}
在这里插入图片描述


被static修饰的变量a存放在静态区,局部变量b存储在栈区,指针p1指向在堆上开辟出的对象,常量字符串的指针存放在代码段

虚表这里,因为a1是一个类对象,它的地址存放了虚表指针和内置类型_a两部分,虚表指针是一个void类型的指针,占4个字节,把它强制转换成int*类型的指针,再解引用,得到的是虚表的指针

虚函数地址就是固定用法,要把是哪个类的虚函数标注出来,然后用取地址符号

从上图我们可以观察到,虚函数和普通函数存放位置接近,代码段和虚表存放位置接近,而虚表和虚函数相对于静态区栈区以及堆区来说还是离代码段更近近,也就是说,虚函数和普通函数以及虚表存放在代码段

2、多态的原理

classA{public:virtualvoidD(){ cout <<"A : virtual void D()"<< endl;}};classB:publicA{public:virtualvoidD(){ cout <<"B : virtual void D()"<< endl;}};voidfunc(A& ra){ ra.D();}voidtest(){ A a; B b;func(a);func(b);}
在这里插入图片描述


在这里插入图片描述


当ra为A对象时,函数调用时在A的虚表中找到func,当ra为B对象时,函数调用时在B的虚表中找到func,然后调用,这样就实现出了不同对象去完成同一行为时,展现出不同的形态

我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数

classA{public:virtualvoidD(){ cout <<"A : virtual void D()"<< endl;}};classB:publicA{public:virtualvoidD(){ cout <<"B : virtual void D()"<< endl;}};voidfunc(A* p){ p->D();}voidtest(){ A a;func(&a); a.D();}
在这里插入图片描述


在这里插入图片描述


对于多态调用:
p中存的是A对象的指针,将p移动到eax中:
00382571 mov eax,dword ptr [p]

[eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx:
00382574 mov edx,dword ptr [eax]

[edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax:
0038257B mov eax,dword ptr [edx]

call eax中存虚函数的指针,这里可以看出满足多态的调用,不是在编译时确定的,是运行起来以后到对象的中取找的

对于普通调用:
因为不满足多态调用所以是普通函数调用,直接call地址

3、动态绑定和静态绑定

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

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

五、单继承和多继承关系的虚函数表

1、单继承中的虚函数表

classA{public:virtualvoidfunc1(){ cout <<"A::func1"<< endl;}virtualvoidfunc2(){ cout <<"A::func2"<< endl;}private:int _a;};classB:publicA{public:virtualvoidfunc1(){ cout <<"B::func1"<< endl;}virtualvoidfunc3(){ cout <<"B::func3"<< endl;}virtualvoidfunc4(){ cout <<"B::func4"<< endl;}private:int _b;};intmain(){ A a; B b;return0;}
在这里插入图片描述


图中的监视窗口中我们发现看不见func3和func4,这里是编译器的监视窗口故意隐藏了这两个函数

那我们如何查看整个b的虚表呢

typedefvoid(*VFPTR)();voidPrintVTable(VFPTR vTable[]){// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数 cout <<"虚表地址>"<< vTable << endl;for(int i =0; vTable[i]!=nullptr;++i){printf("第%d个虚函数地址 :0X%x,->", i, vTable[i]); VFPTR f = vTable[i];f();} cout << endl;}intmain(){ A a; B b; VFPTR * vTablea =(VFPTR*)(*(int*)&a);PrintVTable(vTablea); VFPTR* vTableb =(VFPTR*)(*(int*)&b);PrintVTable(vTableb);return0;}

PrintVTable函数:
核心点就是这个函数指针VFPTR,这是一个函数指针,指向的类型是void*,参数为(),也就是无参,也就是说这个指针可以指向任意一个返回类型为void*并且无参的函数

PrintVTable函数的参数也可以写成VFPTR* vTable,虚表的地址就是指针vTable,后加[]就是对表中的指针进行访问,打印出它们的指针,并且将这些指针指向的函数调用表示出来,让我们可以看到这个地址对应的是哪个函数

main函数:
取出a、b对象的头4bytes,就是虚表的指针,虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr
1.先取a的地址,强转成一个int*的指针

2.再解引用取值,就取到了a对象头4bytes的值,这个值就是指向虚表的指针

3.再强转成 VFPTR* ,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组

4.虚表指针传递给PrintVTable进行打印虚表

5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题,我们只需要点重新生成解决方案就行

2、多继承中的虚函数表

classA1{public:virtualvoidfunc1(){ cout <<"A1::func1"<< endl;}virtualvoidfunc2(){ cout <<"A1::func2"<< endl;}private:int a1;};classA2{public:virtualvoidfunc1(){ cout <<"A2::func1"<< endl;}virtualvoidfunc2(){ cout <<"A2::func2"<< endl;}private:int a2;};classB:publicA1,publicA2{public:virtualvoidfunc1(){ cout <<"B::func1"<< endl;}virtualvoidfunc3(){ cout <<"B::func3"<< endl;}private:int b;};typedefvoid(*VFPTR)();voidPrintVTable(VFPTR vTable[]){ cout <<"虚表地址>"<< vTable << endl;for(int i =0; vTable[i]!=nullptr;++i){printf("第%d个虚函数地址 :0X%x,->", i, vTable[i]); VFPTR f = vTable[i];f();} cout << endl;}intmain(){ B b; VFPTR* vTablea1 =(VFPTR*)(*(int*)&b);PrintVTable(vTablea1); VFPTR* vTablea2 =(VFPTR*)(*(int*)((char*)&b +sizeof(A1)));PrintVTable(vTablea2);return0;}
在这里插入图片描述

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中,也就是func3函数,第一个继承基类就是最左边继承的这个基类

在这里插入图片描述

六、多态中的一些小tips

内联函数可以是虚函数,但是如果被inline修饰的函数是虚函数,那么inline特性将会消失,被修饰的函数相当于没被修饰

静态成员不可以是虚函数,因为静态成员没有this指针使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

构造函数不能是虚函数,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

最好把基类的析构函数定义为虚函数,因为如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这会导致派生类部分的对象没有被正确析构,可能会引发资源泄露

对象在访问虚函数与普通函数速度的对比,如果是普通对象访问,两者一样快,如果是多态对象访问(指针对象或者引用对象),则调用普通函数更快,因为虚函数构成多态,运行时需要到虚函数表中去查找

虚函数表在编译阶段就生成了


今日分享就到这里了~

在这里插入图片描述

Read more

EnvPilot:一款基于 Rust 的跨平台环境变量神器,一键搞定 Windows/Linux 环境配置!

EnvPilot:一款基于 Rust 的跨平台环境变量神器,一键搞定 Windows/Linux 环境配置!

文章目录 * 1. 项目介绍🎯 * 1.1. 什么是 EnvPilot? * 1.2. 为什么选择 EnvPilot? * 2. 核心优势:四大痛点全部解决!💪 * ✅ 痛点一:添加不生效?已修复! * ✅ 痛点二:删除删不掉?已修复! * ✅ 痛点三:PATH 清理失效?已修复! * ✅ 痛点四:误操作无法恢复?已解决! * 3. 支持的开发环境🛠️ * 4. 详细使用教程📖 * 4.1. Windows 平台使用教程 * 1️⃣ 下载安装 * 2️⃣ 配置环境变量 * 3️⃣ 清除环境变量 * 4.2. Linux 平台使用教程 * 1️⃣ 从源码编译 * 2️⃣ 配置环境变量 * 3️

By Ne0inhk

RocketMQ与RabbitMQ全方位深度对比分析

文章目录 * 前言 * 一、设计基因:根本差异的源头 * 1.1 出身与定位 * 1.2 设计哲学的本质差异 * 1.3 核心优势领域 * 二、架构与消息模型:从底层机制看差异 * 2.1 存储引擎差异 * RocketMQ:CommitLog + ConsumeQueue * RabbitMQ:队列 + Mnesia * 2.2 核心组件对比 * 2.3 消息路由机制对比 * RocketMQ:Topic + Tag 二级过滤 * RabbitMQ:Exchange 多样化路由 * 2.4 消息流转完整链路 * RocketMQ 消息流转 * RabbitMQ 消息流转 * 三、集群架构与高可用机制 * 3.1

By Ne0inhk

ArduPilot/PX4 开源飞控架构全解析(附开发实战指引)

一、前言:为什么要懂飞控架构? ArduPilot(APM)和 PX4 是无人机领域最主流的两大开源飞控固件,覆盖消费级、工业级无人机全场景。理解它们的架构: * 能快速定位飞控功能开发的入口,避免 “盲人摸象”; * 掌握不同飞控的设计理念,选择适配自己场景的方案; * 高效进行二次开发(如新增传感器适配、自定义飞行模式); * 排查飞控运行异常、优化飞行性能。 本文将从核心架构设计、代码组织、任务调度、模块通信四个维度,深度拆解 ArduPilot 和 PX4 的架构差异,并给出开发实战指引。 二、ArduPilot/PX4 核心定位与设计理念 先明确两大飞控的核心差异,避免从一开始就混淆: 维度ArduPilot(APM)PX4(Pixhawk)核心定位通用型飞控,适配多载体(多旋翼 / 固定翼 / 车 / 船)高性能飞控,聚焦无人机,强调实时性和模块化开发语言主要

By Ne0inhk
【MySQL】存储引擎 - InnoDB详解

【MySQL】存储引擎 - InnoDB详解

📢博客主页:https://blog.ZEEKLOG.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢本文由 JohnKi 原创,首发于 ZEEKLOG🙉 📢未来很长,值得我们全力奔赴更美好的生活✨ 文章目录 * 🏳️‍🌈一、InnoDB存储引擎的特性 * 🏳️‍🌈二、|nnoDB 的主要优势 * 🏳️‍🌈三、InnoDB表的最佳实践 * 🏳️‍🌈四、验证InnoDB是否为默认存储引擎 * 🏳️‍🌈五、创建InnoDB表 * 👥总结 InnoDB是一款兼顾高可靠性和高性能的通用存储引擎。在MySQL8.0中默认的存储引擎是 InnoDB。 使用 CREATE TABLE 语句创建表时,在没有修改默认存储引擎或明确指定其他存储引擎时,

By Ne0inhk