【C++】早绑定、析构与多态 | 一道关于多态的选择题记录

news2024/12/27 21:01:40

今天在和群友聊天的时候看到了一道很坑的题目,分享给大家

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 "

image-20230822211806379

编译运行,可以看到,结果是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空间,会产生内存泄露;

但构造函数还和虚函数表的初始化有关系,此时虚函数表还没有完全初始化,子类对象尚未构造完成,没有多态调用的条件,所以也不能调用到子类重写后的虚函数。

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

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

相关文章

ARM,(cortex-A7核中断实验)

1.实验目的&#xff1a;实现KEY1/LEY2/KE3三个按键&#xff0c;中断触发打印一句话&#xff0c;并且灯的状态取反&#xff1b; key1 ----> LED3灯状态取反&#xff1b; key2 ----> LED2灯状态取反&#xff1b; key3 ----> LED1灯状态取反&#xff1b; 2.分析框图: …

<深度学习基础> 激活函数

为什么需要激活函数&#xff1f;激活函数的作用&#xff1f; 激活函数可以引入非线性因素&#xff0c;可以学习到复杂的任务或函数。如果不使用激活函数&#xff0c;则输出信号仅是一个简单的线性函数。线性函数一个一级多项式&#xff0c;线性方程的复杂度有限&#xff0c;从…

vue3 01-setup函数

1.setup函数的作用: 1.是组合式api的入口2.比beforeCreate 执行更早3.没有this组件实例一开始创建vue3页面的时候是这样的 <template></template> <script> export default{setup(){return{ }} } </script>给容器传参在页面中显示 数据给模板使用,以…

【C语言】位段,枚举和联合体详解

目录 1.位段 1.1 什么是位段 1.2 位段的内存分配 1.3 位段的跨平台问题 2.枚举 2.1 枚举类型的定义 2.2 枚举的优点 3. 联合&#xff08;共用体&#xff09; 3.1 联合类型的定义 3.2 联合的特点 3.3 联合大小的计算 1.位段 1.1 什么是位段 位段的声明和结构体是类…

基于ECharts+flask的爬虫可视化

项目效果。 本案例基于python的flask框架&#xff0c;通过爬虫程序将数据存储在csv文件中&#xff0c;在项目运行时会通过render_template映射出对应的页面&#xff0c;并且触发一个函数&#xff0c;该函数会读取csv文件的数据将之交给echarts渲染 &#xff0c;echarts将之渲染…

线性代数的学习和整理---番外1:EXCEL里角度,弧度,三角函数

目录 1 角的度量&#xff1a;角度和弧度 1.1 角度 angle 1.1.1 定义 1.1.2 公式 1.1.2 角度取值范围 1.2 弧长和弦长 1.3 弧度 rad 1.3.1 弧长和弧度定义的原理 1.3.2 定义 1.3.3 取值范围 1.3.4 取值范围 1.4 角度&#xff0c;弧度的换算 1.5 EXCEL里进行角度和…

Red Hat Enterprise Linux (RHEL) 6.4 安装、redhat6.4安装

1、下载地址 Red Hat Enterprise Linux (RHEL) 6.4 DVD ISO 迅雷下载地址http://rhel.ieesee.net/uingei/rhel-server-6.4-x86_64-dvd.iso 2、创建虚拟机 3、redhat安装 选择第一个安装 Skip跳过检查 语言选择简体中文 键盘选择默认 选择基本存储设备 忽略所有数据 设置root密…

Ribbon:listOfServers

解释&#xff1a; 配置了address的地址,请求会走address&#xff0c;也就是http://127.0.0.1:8081&#xff0c;通常用户与别的后端服务进行联调设置为其本地服务的ip。 如果将address:注释掉。 会走后面的XXX.feign.default-server地址&#xff0c;这个地址通常可以配一个网关…

基于Spring Boot的智慧团支部建设网站的设计与实现(Java+spring boot+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于Spring Boot的智慧团支部建设网站的设计与实现&#xff08;Javaspring bootMySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java sp…

【动手学深度学习】--18.图像增广

文章目录 图像增广1.常用的图像增广方法1.1翻转和裁剪1.2改变颜色1.3结合多种图像增广方法 2.使用图像增广进行训练3.训练 图像增广 官方笔记&#xff1a;图像增广 学习视频&#xff1a;数据增广【动手学深度学习v2】 图像增广在对训练图像进行一系列的随机变化之后&#xff…

开发一个npm组件包

vue项目初始化 vue create mytest 启动项目以后 组件开发 开发的组件写在 package中 如下如例 开发一个 listpage的组件 里面放了一个a链接注册组件配置打包 "package": "vue-cli-service build --target lib ./src/package/index.js --name managerpage -…

linux 免交互

Linux 免交互 1、免交互概念2、基本免交互的例子2.1命令行免交互统计2.2使用脚本免交互统计2.3使用免交互命令打印2.4免交互修改密码2.5重定向查看2.6重定向到指定文件2.7重定向直接指定文件2.8使用脚本完成重定向输入2.9免交互脚本完成赋值变量2.10关闭变量替换功能&#xff0…

一分钟学算法-递归-斐波那契数列递归解法及优化

一分钟学一个算法题目。 今天我们要学习的是用递归算法求解斐波那契数列。 首先我们要知道什么是斐波那契数列。 斐波那契数列&#xff0c;又称黄金分割数列&#xff0c;是一个经典的数学数列&#xff0c;其特点是第一项&#xff0c;第二项为1&#xff0c;后面每个数字都是前…

linux iptables安全技术与防火墙

linux iptables安全技术与防火墙 1、iptables防火墙基本介绍1.1netfilter/iptables关系1.2iptables防火墙默认规则表、链结构 2、iptables的四表五链2.1四表2.2五链2.3四表五链总结2.3.1 规则链之间的匹配顺序2.3.2 规则链内的匹配顺序 3、iptables的配置3.1iptables的安装3.2i…

echarts范围限制下性能问题

最近实习遇到一个问题&#xff0c;需要对折线图的数据进行范围限制&#xff0c;比如将超过100的设置为100&#xff0c;低于0的设置为0&#xff1b; 原来的代码是创建一个数组&#xff0c;然后遍历原数组&#xff0c;超过的push100&#xff0c;低于0的push0&#xff0c;在中间的…

python内置函数的源码去哪里找?

python内置函数的源码去哪里找&#xff1f; 我们使用的python内置函数&#xff0c;ctrl鼠标左键&#xff0c;进入源码builtins.py发现&#xff0c;具体的函数实现均是pass了&#xff0c;那这些内置函数的源码去哪里找呢&#xff1f; 研究了一番&#xff0c;发现python语言是c…

R-Meta分析核心技术教程

详情点击链接&#xff1a;全流程R-Meta分析核心技术教程 一&#xff0c;Meta分析的选题与检索 1、Meta分析的选题与文献检索 1)什么是Meta分析 2)Meta分析的选题策略 3)精确检索策略&#xff0c;如何检索全、检索准 4)文献的管理与清洗&#xff0c;如何制定文献纳入排除标准 …

边缘计算网关是如何提高物联网的效率的?

随着物联网的持续发展&#xff0c;物联网应用的丰富和规模的扩大&#xff0c;带来了海量的数据处理、传输和计算需求。 传统的“数据中央处理”模式越来越难以适应物联网的扩展速度&#xff0c;在这一趋势下&#xff0c;边缘计算在物联网系统的部署运营中就发挥出了显著的增效…

idea连接linux远程docker详细教程操作

1&#xff1a;修改docker配置文件docker.service vi /usr/lib/systemd/system/docker.service2&#xff1a;找到 ExecStart&#xff0c;在最后面添加 -H tcp://0.0.0.0:2375 # for containers run by docker ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/…

【分布式技术专题】「分布式ID系列」百度开源的分布式高性能的唯一ID生成器UidGenerator

UidGenerator是什么 UidGenerator是百度开源的一款分布式高性能的唯一ID生成器&#xff0c;更详细的情况可以查看官网集成文档 uid-generator是基于Twitter开源的snowflake算法实现的一款唯一主键生成器(数据库表的主键要求全局唯一是相当重要的)。要求java8及以上版本。 snow…