C++ 多态篇
文章目录
- 1. 多态的概念和实现
- 1.1 概念
- 1.2 实现
- 1.2.1 协变
- 1.2.2 析构函数
- 1.2.3 子类虚函数不加`virtual`
- 2. C++11 `final`和`override`
- 3.1 `final`
- 3.2 `override`
- 3. 函数重载、重写与隐藏
- 4. 多态的原理
- 5. 抽象类
- 6.单继承和多继承的虚表
- 6.1 单继承
- 6.2 多继承
- 7. 菱形继承的虚表(了解)
- 7.1 菱形继承
- 7.2 菱形虚拟继承
- 8. 多态面试题
1. 多态的概念和实现
1.1 概念
面向对象语言三大特性之一:多态,意思是多种形态,当不同的对象去做同一件事时会有不同的状态/结果
1.2 实现
多态要在继承的关系中实现,需要满足2个条件:
- 子类必须完成父类虚函数的重写
- 必须通过父类的指针/引用去调用虚函数

被virtual修饰的成员函数叫做虚函数
子类对父类虚函数的重写需要满足三同,即函数名、返回值、参数类型相同,但有三种情况例外
- 协变:返回值可以是其他父子类或自身的指针/引用
- 析构函数
- 子类重写父类虚函数时可以不加
virtual
我们分别对这三点做解释
1.2.1 协变
父子类的返回值可以不相同,但必须是其他父子类后者自身类的指针/引用

1.2.2 析构函数
在继承的章节,我们说析构函数名被统一处理为destructor(),但没说具体原因,其实是要对析构函数设计多态的原因

在上面的场景中,ptr1和ptr2分别指向动态开辟出来的A对象和B对象;B对象中的一个成员,指向动态开辟出来的内存;将来我们要手动释放ptr1和ptr2指向的内存,于是使用delete ptr1和delete ptr2,但根据结果,没能释放掉B对象,于是导致了内存泄漏的问题
我们希望的是指针指向哪个对象,就delete哪个对象的析构,这就要求我们对析构函数实现多态调用,而多态调用要满足三同,其中函数名已经不满足了,于是编译器就将父子类的析构函数重命名为destructor(),只要我们将析构函数定义为虚函数,就满足多态了
因此,父子类的析构函数推荐加上virtual

1.2.3 子类虚函数不加virtual

可以看到,子类虚函数可以不加virtual修饰,此时BuyTicket函数也满足多态,这是因为重写的本质是对父类虚函数实现的重写;用一道例题来更好的理解这点
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()
{B* p = new B;p->test();return 0;
}
// A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

2. C++11 final和override
3.1 final
如果希望一个类不能被继承,有两种方式:
- 将类的构造函数私有化,这是C++98的做法
- 将类进行
final修饰,被修饰的类叫做最总类,这是C++11的做法

3.2 override
override关键字用于检查子类的虚函数是否完成父类虚函数的重写

3. 函数重载、重写与隐藏

4. 多态的原理

上述代码的结果出乎我们的意料,为什么A对象的大小是12字节?经过调试,我们发现A对象中多了一个指针_vfptr
直接给出结论,如果类中有虚函数,那么该类实例化出的对象都要有一个指针,我们把该指针称为虚表指针(在构造函数初始化列表中初始化);该指针指向一个虚表,虚表是一个函数指针数组,存放着虚函数的地址
子类对象会将父类的虚函数拷贝到自己的虚表中,并检查是否完成虚函数的重写,如果完成,将虚函数覆盖为自己的虚函数,当使用父类的引用/指针去调用虚函数时,由于发生切片,如果指向父类,去调用父类的虚函数,如果指向子类,去调用子类中父类部分的虚函数,这就是多态的原理

多态调用和正常调用区别:当进行编译时,编译器会检查函数是否满足多态,如果满足,那么会在执行代码时去对象的虚表中找虚函数调用,如果不满足,那么在编译时就已经确定要调用函数的地址
在这里要分清楚几个概念:
- 虚函数不在虚表中,虚表中存放的是虚函数的地址,虚函数存放在常量区(代码段)上
- 虚表存放在常量区上,下面有代码验证
- 虚基表是继承中存放虚基类偏移量的,用来解决菱形继承数据冗余和二义性的问题

5. 抽象类

在虚函数后加上 =0,表示该虚函数是纯虚函数,有纯虚函数的类叫做抽象类
抽象类在现实生活中没有对应的实体,因此无法实例化出对象;继承抽象类的子类同样也无法实例化出对象,这就强制要求我们完成父类虚函数的重写

6.单继承和多继承的虚表
6.1 单继承

运行上述代码,对比监视窗口和内存窗口,父类的func1和func2都继承了下来,重写了func1,于是将父类的func1覆盖;子类自身的虚函数直接往父类部分的虚表中放,监视窗口没显示func3和func4是因为vs监视窗口的设计问题,在内存中是能看到还有两个指针,这两个指针指向的就是func3和func4,用一份代码来证明

6.2 多继承

在多继承中,子类中有两个虚表指针,分别在A类的部分和B类的部分中,A类的部分继承A类的虚函数,B类的部分继承B类的虚函数,再看是否完成重写,有就拿自身的覆盖;自身的虚函数默认往先继承类的虚表中放

为什么要有多继承对象要有两张虚表,不能把所有的虚函数放在一张虚表中吗?有这样的场景,分别有两个父类的指针指向同一个子类对象,通过任何一个父类指针都应该调用子类的虚函数,但如果只有一张虚表,就变成只有其中一个父类指针能调用子类的虚函数

7. 菱形继承的虚表(了解)
7.1 菱形继承


7.2 菱形虚拟继承


8. 多态面试题
class A
{
public:A(const char* s){ cout << s << endl; }
};class B :virtual public A
{
public:B(const char* s1,const char* s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:C(const char* s1, const char* s2):A(s1) { cout << s2 << endl; }
};class D :public B, public C
{
public:D(const char* s1, const char* s2, const char* s3, const char* s4):B(s1, s2),C(s1, s3),A(s1){cout << s4 << endl;}
};int main()
{D* p = new D("class A", "class B", "class C", "class D");delete p;return 0;
}// A:class A class B class C class D
// B:class D class B class C class A
// C:class D class C class B class A
// D:class A class C class B class D// 答案:A
由此可以证明:初始化列表的初始化顺序是按变量在内存中声明的顺序或类在内存中继承的顺序(先继承的类先初始化);同时同一个对象中,同一个类只会初始化一次
-
inline函数可以是虚函数吗?
可以,但是编译器会忽略inline属性,因为虚函数要放到虚表中
-
静态成员函数可以是虚函数吗?
不可以,静态成员函数属于整个类,无论是通过父类或子类调用,调用的都是同一个静态成员函数;静态成员函数在编译时就已经确定地址,而虚函数要到运行时去虚函数表中找
-
构造函数可以是虚函数吗?
不可以,虚表指针在构造函数的初始化列表中初始化,如果构造函数时虚函数,虚表指针就无法初始化了
-
对象访问普通函数快还是虚函数快?
如果是普通对象,一样快;如果是对象的指针/引用且满足多态,则调用普通函数快,调用虚函数需要在虚表中找
-
虚函数表在什么阶段生成?存在哪里?
虚函数表在编译阶段生成,存放在常量区
相关文章:
C++ 多态篇
文章目录 1. 多态的概念和实现1.1 概念1.2 实现1.2.1 协变1.2.2 析构函数1.2.3 子类虚函数不加virtual 2. C11 final和override3.1 final3.2 override 3. 函数重载、重写与隐藏4. 多态的原理5. 抽象类6.单继承和多继承的虚表6.1 单继承6.2 多继承 7. 菱形继承的虚表(了解)7.1 菱…...
【LVGL-SquareLine Studio】
LVGL-SquareLine Studio ■ SquareLine Studio-官网下载地址■ SquareLine Studio-参考博客■ SquareLine Studio-安装■ SquareLine Studio-汉化■ SquareLine Studio-■ SquareLine Studio-■ SquareLine Studio-■ SquareLine Studio-■ SquareLine Studio- ■ SquareLine S…...
mysqli 与mysql 区别和联系, 举例说明
mysqli是一种PHP的扩展,用于与MySQL数据库进行交互。它提供了一套面向对象的接口,可以更方便地操作数据库。MySQL是一种关系型数据库管理系统,用于存储和管理数据。 区别: mysqli是MySQL的扩展,而不是单独的数据库管…...
【SpringCloud应用框架】Nacos安装和服务提供者注册
第二章 Spring Cloud Alibaba Nacos之Nacos安装和服务提供者注册 文章目录 Nacos介绍为何使用Nacos?一、Nacos下载和安装1. 下载2. 安装Linux/Unix/MacWindows 二、Nacos服务提供者注册1. Nacos代替Eureka2. Nacos服务注册中心3. 引入Nacos Discovery进行服务注册/发…...
英语学习交流小程序的设计
管理员账户功能包括:系统首页,个人中心,用户管理,每日打卡管理,备忘录管理,学习计划管理,学习资源管理,论坛交流 微信端账号功能包括:系统首页,学习资源&…...
实现Java多线程中的线程间通信
实现Java多线程中的线程间通信 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 1. 线程间通信的基本概念 在线程编程中,线程间通信是指多个线程之间通过共享内存或消息传递的方式进行交…...
C++模板元编程(一)——可变参数模板
这个系列主要记录C模板元编程的常用语法 文章目录 引言语法应用函数模板可变参数的打印可变参数的最小/最大函数 类模板 参考文献 引言 在C11之前,函数模板和类模板只支持含有固定数量的模板参数。C11增强了模板功能,允许模板定义中包含任意个(包括0个)…...
kafka中
Kafka RocketMQ概述 RabbitMQ概述 ActiveMQ概述 ZeroMQ概述 MQ对比选型 适用场景-从公司基础建设力量角度出发 适用场景-从业务场景出发 Kafka配置介绍 运行Kafka 安装ELAK 配置EFAK EFAK界面 KAFKA常用术语 Kafka常用指令 Kafka中消息读取 单播消息 group.id 相同 多播消息 g…...
Android 获取当前电池状态
在 API 级别 23 上获取充电状态 要在 API 级别 23 上获取电池的当前状态,只需使用电池管理器系统服务: BatteryManager batteryManager (BatteryManager) getSystemService(BATTERY_SERVICE); boolean isCharging batteryManager.isCharging();使用 S…...
【JVM 的内存模型】
1. JVM内存模型 下图为JVM内存结构模型: 两种执行方式: 解释执行:JVM是由C语言编写的,其中有C解释器,负责先将Java语言解释翻译为C语言。缺点是经过一次JVM翻译,速度慢一点。JIT执行:JIT编译器…...
【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01—短信/邮件/异常/MD5
持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01 环境搭建验证码倒计时短信服务邮件服务验证码短信形式:邮件形式: 异常机制MD5参考 环境搭建 C:\Windows\System32\drivers\etc\hosts 192.168.…...
geom buffer制作
1. auto buffer_geom line_string->buffer(15);//buffer //这个是x和y各扩大段15个单位 auto buffer_geom line_string->buffer(15);//buffer //这个是x和y各扩大段15米 获取buffer坐标 auto boundary buffer_geom->getBoundary(); auto boundary_coords boun…...
微软正在放弃React
最近,微软Edge团队撰写了一篇文章,介绍了微软团队如何努力提升Edge浏览器的性能。但在文中,微软对React提出了批评,并宣布他们将不再在Edge浏览器的开发中使用React。 我将详细解析他们的整篇文章内容,探讨这一决定对…...
U盘非安全退出后的格式化危机与高效恢复策略
在数字化时代,U盘作为数据存储与传输的重要工具,其数据安全备受关注。然而,一个常见的操作失误——U盘没有安全退出便直接拔出,随后再插入时却遭遇“需要格式化”的提示,这不仅让用户措手不及,更可能意味着…...
安卓虚拟位置修改
随着安卓系统的不断更新,确保软件和应用与最新系统版本的兼容性变得日益重要。本文档旨在指导用户如何在安卓14/15系统上使用特定的功能。 2. 系统兼容性更新 2.1 支持安卓14/15:更新了对安卓14/15版本的支持,确保了软件的兼容性。 2.2 路…...
大数据面试题之Presto[Trino](5)
目录 Presto的扩展性如何? Presto如何与Hadoop生态系统集成? Presto是否可以连接到NoSQL数据库? 如何使用Presto查询Kafka中的数据? Presto与Spark SQL相比有何优势和劣势? Presto如何与云服务集成࿱…...
对编程开发人员在今年的一些建议
一、今年的大环境 这几天身体不太好,又不断看到地狱级的就业问题。所以有些想法想和大家分享一下,并提出自己的一些想法和建议。今年的大环境不好,做为非专业人士,咱们也不分析,以免贻笑大方。但针对大环境下的计算机…...
VSCode设置好看清晰的字体!中文用鸿蒙,英文用Jetbrains Mono
一、中文字体——HarmonyOS Sans SC 1、下载字体 官网地址:https://developer.huawei.com/consumer/cn/design/resource/ 直接下载:https://communityfile-drcn.op.dbankcloud.cn/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20230517…...
SpringBoot新手快速入门系列教程四:创建第一个SringBoot的API
首先我们用IDEA新建一个项目,请将这些关键位置按照我的设置设置一下 接下来我将要带着你一步一步创建一个Get请求和Post请求,通过客户端请求的参数,以json格式返回该参数{“message”:"Hello"} 1,先在IDE左上角把这里改为文件模式…...
第1集《修习止观坐禅法要》
《修习止观坐禅法要》诸位法师,诸位学员,阿弥院佛! 我们今天能够暂时放下世间的尘劳,大家在一起研究佛法的课程,这件事情在我们的生命当中是非常的稀有难得。 基本上,我们佛法的修习目的是追求身心的安乐…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
