【C++】早绑定、析构与多态 | 一道关于多态的选择题记录
今天在和群友聊天的时候看到了一道很坑的题目,分享给大家
1.看题!
先来看看题目
struct Dad
{
public:Dad(){ echo();}~Dad(){ echo();}virtual void echo() {cout << "DAD ";}
};struct Son:Dad
{
public:void echo() const override {cout << "SON ";}
};Son ss;
请问这个的输出是什么?
A "DAD DAD "
B "DAD SON "
C "SON DAD "
D "SON SON "
E 编译出错
F 运行出错
答案是E,编译出错!
2.涉及到的知识点
2.1 知识点
先来说说这道题目里面涉及到了什么知识点
- 多态调用;
- 多态重写函数需要满足什么条件;
- 类内函数后加
const
的作用; - 类内函数后加
override
的作用; - 什么是早绑定和晚绑定
一个一个复习吧!
- 多态调用是父类指针/引用指向子类时,调用虚函数会调用子类重写后的版本
- 多态重写函数的条件:函数名/参数/返回值都必须相同(注意还有协变)
- 类内函数后加
const
修饰的是这个对象的this
指针,被修饰的函数中无法修改类内成员变量 - 类内函数后加
override
是让编译器来严格检查是否构成重载 - 早绑定:静态绑定;晚绑定:动态绑定(具体请看CPP多态的博客)
2.2 分析题目
注意看父类和子类中这两个echo()
函数的区别
virtual void echo(){}//父类
void echo() const override {}//子类
首先需要说明的是,子类函数中virtual
关键字是可以省略的,但即便省略了,这个函数依旧是个虚函数。
这里子类的函数中多了const
修饰,而这个const修饰的就是函数中隐含的this
指针,此时子类中echo()
函数的参数就发生了变化!
virtual void echo(Son* this) { } // 不加const
virtual void echo(const Son* this) { } // 加const
正是因为这里的this指针出现了const的修饰,所以子类的echo和父类echo的参数类型不同,不构成虚函数重写!再加上override
关键字的严格检查,会直接编译报错!
正确的写法是删除子类echo中的const或者给父类echo函数加上const
3.再来看题
好了,坑人的点看完了,再来看个「常规」的,就是把上面的题干改成能编译通过的。此时又应该选谁呢?
struct Dad
{
public:Dad(){ echo();}~Dad(){ echo();}virtual void echo() const{cout << "DAD ";}
};struct Son:Dad
{
public:void echo() const override {cout << "SON ";}
};Son ss;
A "DAD DAD "
B "DAD SON "
C "SON DAD "
D "SON SON "
编译运行,可以看到,结果是DAD DAD
,应该选A
3.1 分析
在给 Son
类定义构造函数和析构函数时,没有指定调用父类的对应构造函数和析构函数。因此,在创建 Son
对象 ss
时,会默认调用 Dad
类的构造函数和析构函数。
由于 Dad
类中的构造函数和析构函数调用了虚函数 echo()
,而这个虚函数在子类 Son
中被重写,所以会根据对象类型调用相应的重写函数。然而,在构造函数和析构函数中,虚函数机制不会按照预期工作。
构造函数中调用虚函数时,会忽略动态绑定机制,直接调用父类的函数版本。因此,在 Dad
的构造函数中调用 echo()
,实际上调用的是 Dad
类中的 echo()
函数,而不是 Son
类中的重写版本。
同样地,析构函数中也会忽略动态绑定机制,直接调用父类的函数版本。所以,在 Dad
的析构函数中调用 echo()
,依然调用的是 Dad
类中的 echo()
函数。
因此,当创建 Son
对象 ss
并打印输出时,会先调用 Dad
类的构造函数并打印 "DAD "
,然后调用 Dad
类的析构函数并再次打印 "DAD "
。
3.2 结论
在父类的构造和析构中,对象的版本都被确定为父类的版本,会采用早绑定来调用父类自己的函数,而不是子类的重写后的函数;
简单记忆:父类的构造和析构中如果出现虚函数,只会调用父类自己的函数!
这是因为编译器需要保证正确的构造和析构顺序,如果父类析构里调用子类的虚函数,可能会出现下面的场景
struct Dad
{
public:Dad(){ echo();}~Dad(){ echo();}virtual void echo() const{cout << "DAD ";}
};struct Son:Dad
{
public:Son() {_a = new int(3);}~Son() {delete _a;}void echo() const override {cout << "SON ";delete _a;}
private:int _a;
};Son ss;
如果父类中的析构echo()
调用子类重写的函数,此时就会出现子类已经被销毁(子类的析构函数早于父类析构调用)的_a
被二次delete
,两次delete
同一片空间是会报错的!
所以为了避免这种情况,父类的析构中采用早绑定,子类重写的虚函数不会生效!
这种行为是为了确保在对象的构造和析构过程中,按照正确的顺序调用各个类的构造和析构函数,避免在对象处于未完全初始化或已部分销毁状态时调用子类的函数。
包括父类的构造也可以这么理解,如果父类构造里面可以调用子类的虚函数,可能会出现两次对一个子类对象进行new空间,会产生内存泄露;
但构造函数还和虚函数表的初始化有关系,此时虚函数表还没有完全初始化,子类对象尚未构造完成,没有多态调用的条件,所以也不能调用到子类重写后的虚函数。
相关文章:

【C++】早绑定、析构与多态 | 一道关于多态的选择题记录
今天在和群友聊天的时候看到了一道很坑的题目,分享给大家 1.看题! 先来看看题目 struct Dad { public:Dad(){ echo();}~Dad(){ echo();}virtual void echo() {cout << "DAD ";} };struct Son:Dad { public:void echo() const override…...
mac下安装tomcat
1. 官网下载Apache Tomcat - Apache Tomcat 9 Software Downloads 2. 授权bin目录下所有.sh文件权限sudo chmod 755 *.sh 3. 启动程序(后台运行) sudo sh ./startup.sh 4. 在当前窗口启动程序,随时看到日志sudo sh ./catalina.sh run 5. 关闭程序 sudo sh ./shu…...

【小梦C嘎嘎——启航篇】string常用接口的模拟实现
【小梦C嘎嘎——启航篇】string常用接口的模拟实现😎 前言🙌string 模拟实现1、iterator 迭代器相关使用函数实现2、构造函数接口实现3、 传统写法——拷贝构造函数接口实现4、 现代写法——拷贝构造函数接口实现5、析构函数接口实现6、传统写法—— 赋…...

【Jenkins】持续集成部署学习
【Jenkins】持续集成部署学习 【一】安装部署【1】Jenkins所处位置【2】Docker安装Gitlab(1)首先准备一台空的虚拟机服务器(2)安装服务器所需的依赖(3)Docker的安装(4)阿里云镜像加速…...

Redis数据结构之List
Redis 中列表(List)类型是用来存储多个有序的字符串,列表中的每个字符串成为元素 Eelement),一个列表最多可以存储 2^32-1 个元素。 在 Redis 中,可以对列表两端插入(push)和弹出&am…...

SpringCloud Alibaba实战和源码(7)Skywalking
什么是SkyWalking Skywalking是由国内开源爱好者吴晟开源并提交到Apache孵化器的产品,它同时吸收了Zipkin /Pinpoint /CAT 的设计思路。特点是:支持多种插件,UI功能较强,支持非侵入式埋点。目前使用厂商最多,版本更新较…...
MySQL索引可能失效之or、is null、is not null、不等于(!=,<>)、联合索引
1、如果 A,B 两列都有索引,那么 select * from Table where Aa or Bb; 会走索引吗? 答案:会,因为 A,B都有索引; 2、如果 A,B有索引,但是C没有索引; select * from Table where Aa or Bb …...

无人机电力巡检:探索电力设施维护的新模式
电力巡检一直是电力行业中关键的环节,它的目的是确保电力设施的正常运行和安全稳定,对提高电力设施的可靠性、确保电力供应的稳定性和提高电力企业的管理水平具有重要的意义。传统的电力巡检方式通常采用人工的方式进行,这种方式存在很多的问…...

ethers.js1:ethers的安装和使用
ethers官方文档:Documentation 1、ethers简介: ethers.js是一个完整而紧凑的开源库,用于与以太坊区块链及其生态系统进行交互。如果你要写Dapp的前端,你就需要用到ethers.js。 与更早出现的web3.js相比,它有以下优点…...

小程序中的页面配置和网络数据请求
页面配置文件和常用的配置项 1.在msg.json中配置window中的颜色和背景色 "navigationBarBackgroundColor": "#efefef","navigationBarTextStyle": "black" 2.可以看到home中的没有发生变化但是msg的发生变化了,这个和前面的…...
使用ImageMagick实现多张图片拼接为gif(多线程版)
官网: https://imagemagick.org/ 直接上代码 ExecutorService es Executors.newFixedThreadPool(10); List<File> images getImageFiles(sceneDir); CountDownLatch cdl new CountDownLatch(images.size()); // 拷贝图片 for (File file : images) {System.out.prin…...
解释 RESTful API,以及如何使用它构建 web 应用程序。
RESTful API是一种利用HTTP协议进行通信的Web API设计风格,它采用了一组统一且可缓存的操作,包括GET、POST、PUT、DELETE等,通过URL来定位资源,以及使用JSON、XML等格式来传输数据,以实现系统之间的数据交互和资源共享…...

远程端口转发 实践 如何将物理机某一端口的服务转发到vps上,使得外网能访问到
以本机1470端口(我的sqli-labs)与vps的9023端口为例。 SSH基本的连接命令是: ssh usernamehostname这里牵扯到了两台主机,一是执行命令、运行SSH客户端的主机,我们称为本地主机A【Host A】;二是接收连接请…...
【uniapp 监听键盘弹起与收回】
在uniapp中,可以通过使用小程序提供的API来监听键盘弹起与收回。 首先,在页面的onLoad函数中注册监听事件: onLoad() {uni.onKeyboardHeightChange(this.onKeyboardHeightChange); },然后,在页面的onUnload函数中取消注册监听事…...

【Unity】如何制作小地图
我们为什么要制作小地图呢? 原因很简单: 导航和定位:小地图可以显示玩家当前位置以及周围环境的概览。这使得玩家能够更好地导航和定位自己在游戏中的位置,找到目标或避开障碍物。场景了解:通过小地图,玩…...

基于IMX6ULLmini的linux裸机开发系列八:按键处理实验
目录 GIC相关寄存器 GPIO中断相关寄存器 中断服务函数表 中断向量表偏移位置 make有报错 解决方法:error: for loop initial declarations are only allowed in C99 mode_‘for’ loop initial declarations are only allowed i_Young_2717的博客-CSDN博客 GIC…...
数据结构好题总结
Cut Inequality Down 题解 https://blog.csdn.net/lzh_naive/article/details/103340568 概括:st表倍增类st表 考虑如果没有UL限制的话,相当于是前缀和 我们发现,如果某次到了U/L(相当于是一次碰壁)那么这个值已知…...

Java串口开发
网上搜索了关于java串口开发的资料,发现都不是特别的全,故写下一些心得以帮助其他人能快速上手java串口开发,如有错漏之处,敬请指正 串口开发会用到一个javax.comm和RXTXcomm库,,javax.comm库不支持64位操作系统。该库仅适用于32位操作系统,所以接下来主…...
Python nohup 启动python脚本,后台没有日志
一、情况 1.linux上运行python脚本,前台运行打印日志,后台使用nohup不打印日志。 前台运行 ./xxx.py 后台运行 nohup python ./xxx.py > xxx.log 2>&1 &二、排查思路 2.1 脚本是否有问题 首先看自己写的python脚本是否存在问题。因为…...

完美解决微信小程序使用复选框van-checkbox无法选中
由于小程序使用了vant-ui框架,导致checkbox点击无法选中问题 <van-checkbox value"{{ checked }}" shape"square"><view class"check-content"><view class"checktext">我已阅读并同意>《用户协议》…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...