【C++】多态的原理

目录

一、虚函数表 

1、虚函数表的定义

 2、虚函数表特性

3、虚表的打印

二、多态的原理

三、多态的相关问题

1、指针偏移问题

2、输出的程序是什么?

3、输出的程序是什么?


【前言】

上一篇我们学习了多态的基础知识,这一篇我将带着大家深入多态学习,了解多态的原理。【多态的基本介绍】

一、虚函数表 

1、虚函数表的定义

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

很多人会因为内存对齐认为答案是8,其实不然,答案是12.

这是因为类里面,除了_bb成员,还多一个__vfptr 放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表,存的是虚函数的地址。指针(v 代表 virtual,f 代表 function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表,本质是一个虚函数指针数组,一般情况这个数组最后面放了一个nullptr

 2、虚函数表特性

派生类的虚表是如何形成的呢?

派生类对象中有一个虚表指针,是由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的,另一部分是自己的成员。

a.先将基类中的虚表内容拷贝一份到派生类虚表中

b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数

c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

d.要理解将子类赋值给父类对象时,切片过程中,子类的虚表并没有拷贝切过去。这个过程是不会拷贝子类的虚表的。因为如果拷贝子类的虚表赋值给父类了,那么当指向的对象是这个父类时,到这个父类的虚表里找,找的那就是子类的虚函数了,而不是父类的虚函数

多态是如何利用虚函数表实现指向父类调用父类函数,指向子类调用子类函数的呢?

当指向父类对象时,就会去父类对象的虚表里找虚函数,当指向子类对象时,就会去子类对象的虚表里找虚函数。
中间发生了切割,本质上都是指向了父类数据,看到的还是父类对象。因为派生类继承不仅继承父类的所有数据,也将父类的虚表继承下来了派生类会将重写的虚函数地址覆盖原来的基类的虚函数。这样就可以实现指向父类调用父类函数,指向子类调用子类函数。

同种类型的函数会被放在同一个虚函数表,同类型的对象会指向同一个虚表

Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。 

我们知道要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。反思一下为什么一定要满足这个条件呢?为什么函数直接调用不行呢?

当使用指针或者引用时,父类的对象会指向父类的虚表,子类会继承父类的接口并进行重写指向子类的虚表。这样就可以实现指向父类调用父类函数,指向子类调用子类函数。

对于对象来说,指向父类调用父类函数,指向子类时会面临一个问题,父类的虚表会不会被子类拷贝,如果不拷贝,父类成员的虚表里面永远只有父类的虚函数,这显然是不行的。如果拷贝,虚表指向不明确,是原本父类的虚表还是子类拷贝过来的虚表。所以对象的切片只拷贝成员,不拷贝虚表。

虚函数存在哪的?虚表存在哪的?

虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针

虚表是什么阶段生成的?对象中虚表指针什么时候初始化?

虚表是在编译过程中生成的,因为编译过程中会生成地址。虚表指针是在构造函数中通过初始化列表中初始化。 

理解虚函数为什么要重写?

只有虚函数重写,派生类的虚表里才可以存真正派生类虚函数,因为这个虚表是从父类继承下来的,里面都是父类的虚函数地址。而只有派生类虚函数重写后,才可以将重写的虚函数地址覆盖上去。这样就可以做到指向父类调用父类虚表中对应的虚函数,指向子类,调用子类虚表中对应的虚函数。

3、虚表的打印

 有时候监视器窗口虚表不一定全部显示出来,所以我们可以写一个打印虚表的代码,便于我们自己观察。

虚表是一个函数指针数组,但是函数指针的类型比较复杂,所以我们重定义一下函数指针,增强代码可读性,需要注意:typedef void(*)() VF_PTR 函数指针定义名字需要放在中间

 不同对象虚表里面对象不一样多,vs编译器在虚表最后都会放置一个空,所以我们可以利用这个原理实现for循环。

二、多态的原理

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{p.BuyTicket();
}
int main()
{Person mike;Func(mike);Student johnson;Func(johnson);return 0;
}

当p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。当p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

我们需要知道满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

三、多态的相关问题

1、指针偏移问题

class Base1 {  public:  int _b1; };class Base2 {  public:  int _b2; };class Derive : public Base1, public Base2 { public: int _d; };int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;}A:p1 == p2 == p3   B:p1 < p2 < p3    C:p1 == p3 != p2   D:p1 != p2 != p3

【答案】选C 

2、输出的程序是什么?

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;}A: A->0    B: B->1   C: A->1   D: B->0   E: 编译出错    F: 以上都不正确

 【答案】选B

首先判断是否形成多态:

1、虚函数的重写--三同(函数名、参数、返回值)2、父类指针或者引用调用。

是多态。p调用 test函数,test 调用 func函数,func 函数参数的类型是A* this,this 调用class B的 func,派生类继承父类的成员函数,也会继承成员函数的缺省参数,所以val=1.

3、输出的程序是什么?

class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
};
class B : public A
{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }virtual void test() { func(); }
};
int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}A: A->0    B: B->1   C: A->1   D: B->0   E: 编译出错    F: 以上都不正确

【答案】选D

首先判断是否形成多态:

1、虚函数的重写--三同(函数名、参数、返回值)2、父类指针或者引用调用。

不是多态,test 直接在class B调用func函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/789174.shtml

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

相关文章

HarmonyOS 应用开发之通过数据管理服务实现数据共享静默访问

场景介绍 典型跨应用访问数据的用户场景下&#xff0c;数据提供方会存在多次被拉起的情况。 为了降低数据提供方拉起次数&#xff0c;提高访问速度&#xff0c;OpenHarmony提供了一种不拉起数据提供方直接访问数据库的方式&#xff0c;即静默数据访问。 静默数据访问通过数据…

Incus:新一代容器与虚拟机编排管理引擎

Incus是什么&#xff1f; Incus是一个用于编排管理应用型容器、系统型容器及虚拟机实例的管理工具。它是对 Canonical LXD 的继承与发展&#xff0c;引入了更多的存储驱动支持。 Incus项目的产品地址&#xff1a;Linux Containers - Incus - Introduction 在 LXC-Incus 项目…

KnowLog:基于知识增强的日志预训练语言模型|顶会ICSE 2024论文

徐波 东华大学副教授 东华大学计算机学院信息技术系副系主任&#xff0c;复旦大学知识工场实验室副主任&#xff0c;智能运维方向负责人。入选“上海市青年科技英才扬帆计划”。研究成果发表在IJCAI、ICDE、ICSE、ISSRE、ICWS、CIKM、COLING等国际会议上&#xff0c;曾获中国数…

【turtle海龟先生】神奇的“圆”,画,太极圈,铜钱古币

turtle画圆三步法 步骤: 1、导入turtle库 2、确定半径&#xff0c;画圆(circle ) 3、结束(done ) turtle 库中提供一个直接画圆的函数 turtle.circle&#xff08;半径&#xff09;#半径单位为像素 例&#xff1a; turtle.circle ( 100 ) 表示绘制一个半径为100像素长度的圆形 …

基于ROS的地图发布和加载(GAZEBO仿真)

文章目录 环境配置启动仿真运动控制地图保存地图加载Q&A环境配置 cd ~/catkin_ws/src git clone https://github.com/wh200720041/warehouse_simulation_toolkit.git cd .. catkin_make source ~/catkin_ws/devel/setup.bash启动仿真 roslaunch warehou

【软件工程】详细设计(二)

这里是详细设计文档的第二部分。前一部分点这里 4. 学生端模块详细设计 学生端模块主要由几个组件构成&#xff1a;学生登录界面&#xff0c;成绩查询界面等界面。因为学生端的功能相对来说比较单一&#xff0c;因此这里只给出两个最重要的功能。 图4.1 学生端模块流程图 4.…

软考高级架构师:性能评价方法概念和例题

一、AI 讲解 性能评价是衡量计算机系统或其组件在指定条件下执行预期任务的有效性的一种方式。性能评价的方法主要可以分为几种&#xff0c;每种方法都有其特点和适用场景。 性能评价方法 方法描述时钟频率法通过计算机的时钟频率来评估性能&#xff0c;时钟频率越高&#x…

大话设计模式之状态模式

状态模式是一种行为设计模式&#xff0c;它允许对象在其内部状态发生变化时改变其行为。在状态模式中&#xff0c;对象将其行为委托给当前状态对象&#xff0c;从而在不同的状态下执行不同的行为&#xff0c;而不必在对象自身的代码中包含大量的条件语句。 通常&#xff0c;状…

Tensorboard使用教程

Pytorch(九) —— Tensorboard(当有了tensorboard日志文件怎么可视化它)(同时显示多个模型)(vscode的tensorboard)(TensorboardX)_tensorboard --logdir-CSDN博客文章浏览阅读9.7k次&#xff0c;点赞10次&#xff0c;收藏56次。tensorboard.pyfrom tensorboardX import Summary…

RuntimeError: Error compiling objects for extension虚拟环境和系统环境——添加、删除、修改环境变量

前言&#xff1a;因为一个报错RuntimeError: Error compiling objects for extension 没有配置cl.exe环境变量&#xff0c;我的应用场景是需要搞定虚拟环境变量配置 RuntimeError: Error compiling objects for extension手把手带你解决&#xff08;超详细&#xff09;-CSDN博…

爬虫 红网时刻 获取当月指定关键词新闻 并存储到CSV文件

目标网站&#xff1a;红网 爬取目的&#xff1a;为了获取某一地区更全面的在红网已发布的宣传新闻稿&#xff0c;同时也让自己的工作更便捷 环境&#xff1a;Pycharm2021&#xff0c;Python3.10&#xff0c; 安装的包&#xff1a;requests&#xff0c;csv&#xff0c;bs4&…

如何在pgAdmin中用替换的值更新jsonb列?(二)

上一篇提到怎么替换jsonb&#xff0c;链接如下&#xff1a; 如何在pgAdmin中用替换的值更新jsonb列&#xff1f;-CSDN博客 那么当jsonb嵌套jsonb应该怎么替换呢&#xff1f;像这样&#xff0c;类型依然是jsonb&#xff0c;只不过嵌套一层&#xff0c;JsonData&#xff1a;&qu…

网络安全 | 什么是DDoS攻击?

关注WX&#xff1a;CodingTechWork DDoS-介绍 DoS&#xff1a;Denial of Service&#xff0c;拒绝服务。DDoS是通过大规模的网络流量使得正常流量不能访问受害者目标&#xff0c;是一种压垮性的网络攻击&#xff0c;而不是一种入侵手段。NTP网络时间协议&#xff0c;设备需要…

Springboot整合Milvus向量库

1. Milvus的Maven依赖&#xff0c; 配置如下 <dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.3.4</version><exclusions><exclusion><artifactId>log4j-slf4j-imp…

百度语音识别

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、建号—获取试用KEY二、测试代码三、运行四、运行结果五、验证五、总结 一、建号—获取试用KEY https://console.bce.baidu.com/ai/#/ai/speech/overview/index…

记一次Cannot deploy POJO class [xxx$$EnhancerBySpringCGLIB$$xxx]的错误

最近项目上需要使用websocket做服务端&#xff0c;那好说啊&#xff0c;直接springboot集成的websocket 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><versi…

赛氪网在长沙宣布启动“徽文化外宣翻译大赛”

2024年3月30日下午&#xff0c;在美丽的星城长沙&#xff0c;赛氪网作为承办方&#xff0c;在中国翻译协会年会期间成功举办了备受瞩目的“AI科技时代竞赛与就业分论坛”。此次论坛汇聚了众多翻译界、科技界和教育界的专家学者&#xff0c;共同就科技、实践、就业与竞赛人才培养…

使用C++调用讯飞星火API的详细指南

正文&#xff1a; 科大讯飞是中国领先的人工智能公司&#xff0c;其讯飞星火API为开发者提供了丰富的接口和服务&#xff0c;支持各种语音和语言技术的应用。下面是使用C接入讯飞星火API的步骤和代码示例。 步骤一&#xff1a;注册账号并创建应用 首先&#xff0c;您需要访问科…

蓝色wordpress外贸建站模板

蓝色wordpress外贸建站模板 https://www.mymoban.com/wordpress/7.html

保护你的 Java 代码:深入了解代码混淆

在当今数字化时代&#xff0c;软件开发领域竞争激烈&#xff0c;而保护你的代码免受恶意攻击和盗用是至关重要的。代码混淆是一种常用的技术&#xff0c;用于增加攻击者分析和逆向工程代码的难度&#xff0c;从而提高代码的安全性。本文将介绍代码混淆的基本概念和详细办法&…