C++【多态】

         通俗来说,多态就是指同一个操作或者行为在不同的对象上可以有不同的表现形式或实现方式。举个例子:以 “吃” 这个行为为例,不同的动物有不同的 “吃” 的方式和内容。比如,猫吃鱼、狗吃肉、兔子吃草,虽然都是 “吃” 这个行为,但不同的动物表现出了不同的具体行为和特点,这就是一种多态现象。

        在编程语言里,多态通常体现在函数或方法的调用上。假设有一个父类和几个子类,它们都有一个名为 “draw” 的方法,父类的 “draw” 方法可能只是一个通用的框架,而子类会根据自身的特点重写 “draw” 方法来实现不同的绘图功能。比如,圆形子类的 “draw” 方法用于绘制圆形,矩形子类的 “draw” 方法用于绘制矩形。当使用父类的引用去调用 “draw” 方法时,根据实际指向的是圆形对象还是矩形对象,会调用到不同子类中重写后的 “draw” 方法,表现出不同的绘图效果。

1、多态的构成条件

           1、被调用的函数必须是虚函数,且构成函数重写(覆盖);

           2、必须由父类的指针或者引用来进行调用;

说明:1、成员函数才能被修饰成虚函数,普通的函数无法被修饰为虚函数。

           2、虚函数的重写要求成员函数满足三同条件(函数名相同、参数相同、返回值相同),这里有一个特殊情况叫做协变(返回值可以不同,要求父类返回父类的指针或者引用,子类返回子类的指针或者引用),虚函数的重写仅仅是函数方法的重写。

#include<iostream>
using namespace std;class Person
{
public:virtual void BuyTicket(){cout << "买全价票" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买半价票" << endl;}
};void fuc(Person& p)
{p.BuyTicket();
}int main()
{Person p;Student st;fuc(p);fuc(st);return 0;
}

问题:为什么编译器要将父类和子类的析构函数名称处理成destructor?

这里有这样一个案例,如下面的代码所示:

#include<iostream>
using namespace std;class Person
{
public:~Person(){cout << "~Person()" << endl;}
};class Student : public Person
{
public:~Student(){cout << "~Student()" << endl;}
};int main()
{Person* p = new Person;delete p;p = new Student;delete p;return 0;
}

        这里我们在释放对象的时候,预期先调用~Person(),再调用Student的析构函数。也就是这里期望要形成多态。Person* p 指针指向什么对象就调用该对象的析构函数。而目前的状态下程序的运行结果为:

        也就是这里的函数调用只满足的多态的一个条件(调用使用父类的指针或者引用),那么我们尝试满足形成多态的第二个条件: 构成虚函数的重写,先加上virtual ,这里就会注意到如果编译器不统一进行析构函数名称的处理,那么就无法完成重写的条件(三同)。所以,为了构成多态,编译器就需要将子类和父类的析构函数处理成名称一样的函数。下面我们看形成多态之后的程序运行效果

        根据代码的析构条件,父类先行析构,子类先析构子类的部分,父类部分再调用父类的析构函数。

1.1、重载、重定义与重写

  • 函数重载要求两函数必须在同一作用域,函数名要求一致,参数列表不同。这就构成了函数重载。
  • 重定义是基类与派生类中的成员函数名相同,这种情况也叫做隐藏。
  • 函数重写的要求比较严格,要求基类与派生类中的虚函数保持三同(返回值、函数名、参数列表),在协变这同特殊情况下可以返回值不同。需要注意的的是函数重写仅仅是函数方法的重写。

2、多态的实现原理

2.1、虚函数与虚函数表

        虚成员函数与普通的成员函数一样存放在内存中的代码段部分。在含有虚函数的对对象里会存在一个虚函数表指针。

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};

        sizeof(Base)的大小并非是4个字节,经过在X86环境下验证可以发现sizeof(Base)的大小为8字节。实际上在Base的内部存放了一个虚函数表指针,该指针指向Base的虚函数表。 下面来详细分析一下继承中的虚函数表。

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _a = 1;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _b = 2;
};int main()
{Base b;Derive d;return 0;
}

        观察监视窗口我们可以看到:

        实际上在继承的过程中派生类会将基类的虚函数表进行拷贝,如果存在重写的虚函数,就会将重写之后的虚函数覆盖基类原来的函数。这里会就引出一些问题

        1、为什么多态的实现需要基类的指针或者引用来调用呢?

        假设使用派生类的指针或者引用来调用函数,那么就会直接调用派生类的成员函数,不会体现出多态性。而是用基类的指针和引用时,在编译阶段,编译器并不知道实际要调用的是基类的虚函数版本还是派生类的虚函数版本,而是在运行时根据指针或者引用指向对象的类型来确定具体的版本。

        2、为什么不能使用基类对象直接调用虚函数实现多态呢?

       首先我们要理清楚,使用基类指针或者引用来调用虚函数实现多态的原理。1)当一个派生类对象的指针或者引用直接赋值给基类的指针或引用,从该基类指针看到的派生类对象就是一个以派生类虚函数表指针开头的基类对象,从而在派生类的虚函数表中找到要调用的虚函数。2)当一个基类的对象或者指针调用基类的虚函数时,自然的就会先从基类的虚函数表中找到要调用的虚函数。

       然而,如果使用的是基类对象来调用虚函数,派生类赋值给基类会引发对象的切片操作,这个过程会创建一个新的基类实例,并将派生类中属于基类的数据复制给新创建的基类对象。在这个过程中,派生类的虚函数表并没有被直接删除或者进行修改,仍然存在在内存中。新创建的基类对象有自己独立的内存空间和虚函数表指针,它的虚函数表指针指向基类的虚函数表。这种情况下就不能实现派生类要实现的功能,因此无法实现多态。

        

        

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

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

相关文章

《道德经的启示:人际关系交往的智慧》

第二章:人际关系交往的智慧 🤝 引言:现代人际关系的困境 🌟 时代背景:超连接时代的人际迷思 🌐 在这个前所未有的超连接时代,我们似乎比任何时候都更"在线"、更"联系",但真正的人际连接却越发稀缺。你是否也有过这样的困扰: 🏢 职场上愈是…

一个前端,如何同时联调多个后端

文章目录 场景解决方案思路实现步骤创建项目目标前端配置安装cross-env配置vue.config.js配置package.json 测试 场景 一个前端&#xff0c;需要同时和N个后端联调 一个需求里有若干个模块&#xff0c;分别给不同的后端开发&#xff0c;前端需要和N个后端联调 本地开启一个端…

HTML5+CSS多层级ol标签序号样式问题

在CSS中&#xff0c;ol标签用于创建有序列表&#xff0c;而多层级的ol标签可以通过CSS实现不同的序号样式。以下是一些常见的问题和解决方案&#xff1a; 1. 多层级ol的序号格式问题 默认情况下&#xff0c;多层级的ol标签会自动继承父级的序号格式&#xff0c;但有时我们可能…

DeepSeek全栈技术体系解密:从算法源码到企业级智能体开发实战

在AGI技术加速演进的时代背景下&#xff0c;DeepSeek作为行业级大模型的代表&#xff0c;正在重塑智能系统的开发范式。本课程体系首次系统性披露DeepSeek技术栈的完整实现细节&#xff0c;涵盖从底层算法创新、工程架构设计到企业级落地的全链条知识体系。 课程核心价值矩阵 …

CTA 血管重建,三维重建,血管三维重建

CT检查在临床中应用十分广泛&#xff0c;CT以其扫描速度快&#xff0c;对骨头及钙化敏感而具有部分优势。 CTA是CT血管成像&#xff0c;是CT临床应用中一个非常重要的部分&#xff0c;由于血管及其背景软组织自然对比差&#xff0c;常规CT平扫往往难以显示血管。在行CTA检查的时…

基础排序算法

冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;一种交换排序&#xff0c;它的基本思想是&#xff1a;两两比较相邻记录的关键字&#xff0c;如果反序则交换&#xff0c;直到没有反序的记录为止。 以下代码是改进的冒泡算法&#xff0c;在排序好了之后可以直接跳出循环…

什么是神经网络?

0 前言 神经网络是一种人工智能方法&#xff0c;用于教计算机以受人脑启发的方式处理数据。这是一种机器学习过程&#xff0c;称为深度学习&#xff0c;它使用类似于人脑的分层结构中的互连节点或神经元。它可以创建自适应系统&#xff0c;计算机使用该系统来从错误中进行学习…

MySQL 主从复制原理及其工作过程

一、MySQL主从复制原理 MySQL 主从复制是一种将数据从一个 MySQL 数据库服务器&#xff08;主服务器&#xff0c;Master&#xff09;复制到一个或多个 MySQL 数据库服务器&#xff08;从服务器&#xff0c;Slave&#xff09;的技术。以下简述其原理&#xff0c;主要包含三个核…

Ext系列文件系统 -- 磁盘结构,磁盘分区,inode,ext文件系统,软硬链接

目录 1.理解硬盘 1.1 磁盘、服务器、机柜、机房 1.2 磁盘物理结构 1.3 磁盘的存储结构 1.4 磁盘的逻辑结构 1.4.1 理解逻辑结构 1.4.2 真实过程 1.5 CHS地址和LBA地址的相互转换 2.引入文件系统 2.1 “块”概念 2.2 “分区”概念 2.3 “inode”概念 3.ext2文件系…

C# 背景 透明 抗锯齿 (效果完美)

主要是通过 P/Invoke 技术调用 Windows API 函数 gdi32.dll/user32.dll&#xff0c;同时定义了一些结构体来配合这些 API 函数的使用&#xff0c;常用于处理图形绘制、窗口显示等操作。 运行查看效果 局部放大&#xff0c;抗锯齿效果很不错,尾巴毛毛清晰可见。 using System; u…

Elasticsearch 混合搜索 - Hybrid Search

作者&#xff1a;来自 Elastic Valentin Crettaz 了解混合搜索、Elasticsearch 支持的混合搜索查询类型以及如何制作它们。 本文是三篇系列文章中的最后一篇&#xff0c;深入探讨了向量搜索&#xff08;又称语义搜索&#xff09;的复杂性以及它在 Elasticsearch 中的实现方式。…

【分布式理论12】事务协调者高可用:分布式选举算法

文章目录 一、分布式系统中事务协调的问题二、分布式选举算法1. Bully算法2. Raft算法3. ZAB算法 三、小结与比较 一、分布式系统中事务协调的问题 在分布式系统中&#xff0c;常常有多个节点&#xff08;应用&#xff09;共同处理不同的事务和资源。前文 【分布式理论9】分布式…

Zabbix 7.2实操指南:基于OpenEuler系统安装Zabbix 7.2

原文出处&#xff1a;乐维社区 部署环境 openEuler 22.03 LTS PHP 8.0 Apache Mysql 8.0 MySQL数据库 6.0 以上版本需要安装mysql8.0以上版本的数据库&#xff08;以mysql为例子&#xff09;。 欧拉系统自带 mysql8.0 的源&#xff0c;无需要安装额外的源。 安装mysql …

什么是DeFi (去中心化金融)

DeFi (去中心化金融) 概述 &#x1f4b0; 1. DeFi 基础概念 1.1 什么是 DeFi&#xff1f; DeFi 是建立在区块链上的金融服务生态系统&#xff0c;它&#xff1a; 无需中心化中介开放且透明无需许可即可参与代码即法律 1.2 DeFi 的优势 开放性&#xff1a;任何人都可以参与…

python-leetcode 39.二叉树的直径

题目&#xff1a; 给定一棵二叉树的根节点&#xff0c;返回该树的直径。 二叉树的直径是指中间任意两个节点之间最长路径的长度。这条路径可能经过也可能不经过根节点root 两节点之间路径的长度由他们之间的边数表示 方法一&#xff1a;深度优先搜索 一条路径的长度为该路…

python爬虫系列课程2:如何下载Xpath Helper

python爬虫系列课程2:如何下载Xpath Helper 一、访问极简插件官网二、点击搜索按钮三、输入xpath并点击搜索四、点击推荐下载五、将下载下来的文件解压缩六、打开扩展程序界面七、将xpath.crx文件拖入扩展程序界面一、访问极简插件官网 极简插件官网地址:https://chrome.zzz…

C++17 中的 std::to_chars 和 std::from_chars:高效且安全的字符串转换工具

文章目录 1. 传统转换方法的局限性2. std::to_chars&#xff1a;数值到字符串的高效转换函数原型&#xff1a;返回值&#xff1a;示例代码&#xff1a;输出&#xff1a; 3. std::from_chars&#xff1a;字符串到数值的高效解析函数原型&#xff1a;返回值&#xff1a;示例代码&…

初尝git自结命令大全与需要理解的地方记录

常用命令 git init–初始化工作区touch 文件全称–在工作区创建文档rm 文件全称 --删除文档notepad 文件全称–在工作区打开文档cat 文件全称–在显示框显示文档的东西git status --显示工作区的文件冲突的文件 &#xff08;git add 文件全称或者.&#xff09; —将工作区文件…

Python——生成AIGC图像

文章目录 一、背景介绍 二、效果图展示 三、完整代码 四、分步解释 五、实用建议 1&#xff09;提示词技巧 2&#xff09;性能优化 3&#xff09;常见问题处理 4&#xff09;扩展功能建议 六、注意事项 1. 硬件要求 2. 法律合规 3. 模型安全 一、背景介绍 AIGC&a…

MyBatis框架七:缓存

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 MyBatis缓存介绍 正如大多数持久层框架一样&#xff0c;MyBatis 同样提供了一级缓存和二级缓存的支持 1、一级缓存: 基于PerpetualCache 的 HashMap本地缓存&#xf…