当前位置: 首页 > news >正文

C++相关概念和易错语法(21)(虚函数、协变、析构函数的重写)

多态的核心是虚函数,本文从虚函数出发,根据原理慢慢推进得到结论,进而理解多态

1.虚函数

先看一下下面的代码,想想什么导致了这个结果


#include <iostream>
using namespace std;class A
{
public:virtual void test(){cout << "A" << endl;;}
};class B : public A
{
public:void test(){cout << "B" << endl;;}
};class C : public B
{
public:void test(){cout << "C" << endl;;}
};void Test(A& r)
{r.test();
}int main()
{A a;B b;C c;Test(a);Test(b);Test(c);return 0;
}

结果是

如果我们去掉A里面的virtual呢?

我们可以看到前后两次结果不同,为什么呢?函数形参为什么是以A&来接收的,调用时为什么还有区别呢?这就需要接触虚函数了。

(1)虚函数和虚函数表

当我们在父类声明了一个虚函数后,这个函数就被存在常量区了,同时在这个类里又多了一个新的隐藏成员,叫虚函数表(这个成员要算在整个类的大小里面)。这个虚函数表就是专门存虚函数的地址的(本质是函数指针数组,根据不同机器指针大小也不同)。对于父类而言,无论创建多少对象,它们都共用一个虚函数表(即对于同一种类,函数都是一样的)。这里要分清:虚函数是存在常量区的而不存在类里,类中存的是虚函数表。

(2)重写

虚函数有什么用呢?当子类实现一个和父类虚函数函数名、参数、返回值完全一样的函数时,就叫做重写。重写是一种特殊的隐藏,是在多态中的一种语法,而隐藏只要求函数名相同,是继承中的语法。重写的意义在于子类也有一个新的虚函数表,虽然函数前没有加声明virtual(父类前必须加),当子类显式写了这个函数,就会存到常量区,虚函数表存函数的地址(第一句指令的地址)。对于这个子类,无论创建多少个对象,它们都使用同一个针对子类的虚函数表。如果说有多个虚函数而子类没有重写,那个没有重写的函数就使用父类的对应的函数(反正没区别)。

(3)对多态的理解

到这里,我们对虚函数表、虚函数和重写有了一定了解,实际就是在最初的父类的函数前加上virtual,让该函数进入虚函数表,子类重写会让虚函数表存的函数不同,在调用的时候明明是调用的同一个函数,但得到的结果是针对每一种类不同的。这就叫多态,即多种形态,针对不同的类有不同的表现形态。

(4)对多态调用方式的理解

函数形参为什么是以A&来接收的?

我们进一步关注Test(A& r)这个函数,前面我们讲了赋值兼容转换,因此当B和C传进去的时候,r都会指向子类中的父类部分,这里相当于给它们的父类部分取别名。也就是说,r无论接收的是A还是B还是C,最终都会被切割成A的模样(A中也有虚函数表),但是内容是不是都一样呢?

很明显,虚函数表的作用就凸显出来了,A、B、C都有一个虚函数表,在B、C切割成A后,虚函数表被保留了下来,当我们用r去调用虚函数时,编译器会默认去虚函数表找到对应的函数(三种虚函数表的函数在函数名、参数、返回值上都相同,但存的函数地址不同),根据不同的函数地址就能找到不同的函数实现,这也是重写的意义所在。

至此,我们应该能够理解前面所说虚函数、虚函数表、重写存在的意义了,它们的出现都最终服务于实现一件事——多态,即根据不同类,在调用同一函数时体现出不同状态。

(5)是否有其它调用方式?

事实上,使用A&调用本质就是利用了赋值兼容转换,将多个子类都切割成父类的形式,再根据它们虚函数表的值的差异,调用不同的同名函数,体现出类与类之间的区别。很明显,除了引用,指针也适合,但赋值呢?赋值不是也遵循赋值兼容转换吗?

从实验上看是不行的,但也好理解。r都已经完全变成A类型了,再去调用B或C的成员就不太说得过去了。你可以将这里理解成一种特殊处理,支不支持都说得过去,但从形式上来说不支持更合理。

(6)多态的条件

很多课程都喜欢先说条件再将原因,而如果我们慢慢推进,到这里自然就理解了。

多态需满足条件:父类函数(想和子类形成差异的第一个函数就叫父类函数)写virtual(父类如果不写virtual而子类写virtual,那第一个写virtual的才叫父类,你可以将virtual当作一个多态开始的标志),后续的所有子类写不写virtual无所谓;子类覆盖/重写父类的虚函数;调用时使用父类的指针或引用,特别注意不能用赋值。

2.协变

上面说过要重写函数,必须保证函数的函数名、参数、返回值相同。但有唯一一个例外可以在返回值不同时能构成重写,就是协变(基本不用),即返回值可以是父子类的引用或指针

下面这段代码是能跑过的


#include <iostream>
using namespace std;class A
{
public:virtual A& test(){cout << "A" << endl;;return *this;}
};class B : public A
{
public:B& test(){cout << "B" << endl;;return *this;}
};void Test(A& r)
{r.test();
}int main()
{A a;B b;Test(a);Test(b);return 0;
}

注意,返回值可以加const,返回值也可以是其它类,但必须是父子关系


#include <iostream>
using namespace std;class C
{};class D : public C
{};class A
{
public:virtual const C* test(){cout << "A" << endl;C* c = new C;return c;}
};class B : public A
{
public:const D* test(){cout << "B" << endl;D* d = new D;return d;}
};void Test(A& r)
{r.test();
}int main()
{A a;B b;Test(a);Test(b);return 0;
}

注意父子关系顺序不能反,父类返回值对应父类的虚函数

协变几乎不用,了解即可。我们大部分情况还是要保证函数名、参数、返回值相同,讨论的时候也是跳过这个特殊情况的。

3.析构函数的重写

理解析构函数的重写可以加深我们对析构函数的理解,顺便能够解释为什么所有的析构函数都会被处理成destructor()


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

这段代码会导致内存泄漏,因为当delete a的时候,会根据a的类型去调用析构函数,这里就只会去调用A的析构函数

联系到上面的重写,很快我们就会想到使用virtual修饰父类的析构函数,让析构函数进入虚函数表。但是很明显父类和子类的类名是不可能相同的,所以类的析构函数做了特殊处理:即都重命名为~destructor(),这样就符合了虚函数的要求


我们可以看到,这里根据虚函数表就能成功调到子的析构函数了,同时对于所有继承而言,子的析构调用完成之后都会逐级向上调用父的析构函数

相关文章:

C++相关概念和易错语法(21)(虚函数、协变、析构函数的重写)

多态的核心是虚函数&#xff0c;本文从虚函数出发&#xff0c;根据原理慢慢推进得到结论&#xff0c;进而理解多态 1.虚函数 先看一下下面的代码&#xff0c;想想什么导致了这个结果 #include <iostream> using namespace std;class A { public:virtual void test(){co…...

SoulApp创始人张璐团队以AI驱动社交进化,平台社交玩法大变革

在科技飞速发展的今天,人工智能正逐步渗透到社交媒体的各个环节,赋能全链路社交体验。AI的引入不仅提升了内容推荐的精准度,使用户能够更快速地发现感兴趣的内容,还能通过用户行为预测,帮助平台更好地理解和满足用户需求。此外,AI驱动的虚拟助手和聊天机器人也正在改变用户互动…...

MySQL事务隔离级别+共享锁,排他锁,乐观锁,悲观锁

在操作数据库的时候&#xff0c;可能会由于并发问题而引起的数据的不一致性&#xff08;数据冲突&#xff09;。 MySQL事务隔离级别 一个事务的执行&#xff0c;本质上就是一条工作线程在执行&#xff0c;当出现多个事务同时执行时&#xff0c;这种情况则被称之为并发事务&am…...

Zynq系列FPGA实现SDI编解码转SFP光口传输(光端机),基于GTX高速接口,提供6套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案在Xilinx-Kintex7上的应用 3、详细设计方案设计原理框图输入Sensor之-->OV5640摄像头输入Sensor之-->HDMIVDMA图像缓存RGB转BT1120GTX 解串与串化SMPTE SD/HD/3G SDI IP核BT1120转RGBHDMI输…...

SpringBoot实现图形验证码

目录 项目创建 前端代码实现 约定前后端交互接口 需求分析 接口定义 Hutool工具 实现服务器端代码 引入依赖 获取验证码 验证码校验 调整前端代码 随着安全性的要求越来越高&#xff0c;目前许多项目中都使用了验证码&#xff0c;验证码也有各种类型&#xff0c;如 …...

【JVM基础01】——介绍-初识JVM运行流程

目录 1- 引言&#xff1a;初识JVM1-1 JVM是什么&#xff1f;(What)1-1-1 概念1-1-2 优点 1-2 为什么学习JVM?(Why) 2- 核心&#xff1a;JVM工作的原理&#xff08;How&#xff09;⭐2-1 JVM 的组成部分及工作流程2-2 学习侧重点 3- 小结(知识点大纲)&#xff1a;3-1 JVM 组成3…...

图数据库 - Neo4j简介

深入理解 Neo4j 与 Cypher 语法 什么是 Neo4j Neo4j 是一个基于图的数据库管理系统&#xff0c;它使用图形理论来表示数据关系。这种数据库与传统的关系型数据库不同&#xff0c;它更适合处理高度互联的数据结构。 基本概念 图&#xff1a;在 Neo4j 中&#xff0c;数据以图的…...

C#环境与数据类型

文章目录 C#环境.NET 框架集成开发环境 创建一个C#项目数据类型值类型引用类型对象类型object动态类型dynamic字符串类型string 指针类型 类型转换隐式转换显示转换&#xff08;强制转换&#xff09;C#提供的类型转换方法Convert类Parse方法TryParse方法 C#环境 .NET 框架 C#是…...

jenkins系列-06.harbor

https://github.com/goharbor/harbor/releases?page2 https://github.com/goharbor/harbor/releases/download/v2.3.4/harbor-offline-installer-v2.3.4.tgz harbor官网&#xff1a;https://goharbor.io/ 点击 Download now 链接&#xff0c;会自动跳转到上述github页面&am…...

kotlin get set

在 Kotlin 中&#xff0c;如果想实现一个类的属性可以从外部读取但不能修改&#xff0c;可以使用自定义的 getter 和 private setter。以下是一个示例代码&#xff1a; class MyClass {var myProperty: Stringprivate set // 使 setter 私有化&#xff0c;外部无法修改get // …...

Flask包算法服务

常规包算法服务,就是比较简单,直接起一个fastapi就可以了。 import time import asyncio from aidraw import engineer_log as eng from fastapi import FastAPI from pydantic import BaseModel from typing import Optional from aidraw.ardraw import run_aidraw_api# 起…...

Flowable(一个开源的工作流和业务流程管理引擎)中与事件相关的一些核心概念

Flowable&#xff08;一个开源的工作流和业务流程管理引擎&#xff09;中与事件相关的一些核心概念 Flowable&#xff08;一个开源的工作流和业务流程管理引擎&#xff09;中与事件相关的一些核心概念&#xff0c;包括它们的作用和触发场景。以下是对这些内容的简要说明&#x…...

深度解析:景区客服系统如何助力旅游业可持续发展

一、引言 在全球化与信息化交织的时代背景下&#xff0c;旅游业正以前所未有的速度发展&#xff0c;成为推动经济增长、文化交流与环境保护的重要力量。景区作为旅游业的核心组成部分&#xff0c;其服务质量和管理水平直接影响到游客的满意度和行业的可持续发展。景区客服系统…...

风险评估:IIS的安全配置,IIS安全基线检查加固

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…...

uniapp 截取两条数据 进行页面翻页滚动

// 轮播信息 <view class"sales_list" ><view class"sales_item" v-for"(item,index) in sellDisplayList" :key"index" click"salesFn(item)"><image :src"item.goodsImg"></image><…...

python笔记(转存ipynb)------1

list1 ["tom","cat","Lili"] print(list1[0].title())Tom#append()列表方法在列表末尾添加新元素 list1.append(233) print(list1) #可以先创建空列表&#xff0c;再进行追加append(..)以添加[tom, cat, Lili, 233]#insert()列表方法插入元素 l…...

excel系列(二) - 利用 easypoi 快速实现 excel 文件导入导出

一、介绍 在上篇文章中&#xff0c;我们介绍了 apache poi 工具实现 excel 文件的导入导出。 本篇我们继续深入介绍另一款优秀的 excel 工具库&#xff1a;easypoi。 二、easypoi 以前的以前&#xff0c;有个大佬程序员&#xff0c;跳到一家公司之后就和业务人员聊上了&…...

邀请函|2024第八届中国太阳能电池浆料与金属化技术展

2024第八届中国国际太阳能电池浆料与金属化技术展览会 地点:深圳国际会展中心 时间:2025年06-月25日-27日 地点:上海新国际博览中心 时间:2024年12月18日-20日 主办单位&#xff1a;上海氟伦展览有限公司 指导单位&#xff1a;中国新材料技术协会 中国电子学会 耐…...

图像边缘检测:技术原理与算法解析

图像边缘检测是计算机视觉和图像处理中的一个核心任务&#xff0c;它旨在识别图像中亮度变化明显的点&#xff0c;从而识别出图像的边缘。边缘是图像中的重要特征&#xff0c;对于后续的图像分析、物体识别和图像分割等任务具有至关重要的作用。本文将深入探讨图像边缘检测的技…...

【Python星启航】少儿编程精英启蒙之旅 - 大纲

1. 计算机基础与编程环境 计算机的基本构成 编程语言与编程环境介绍 Python语言的特点与优势 安装与配置Python环境 2. 计算机历史与发展 计算机的起源与早期发展 个人电脑的普及与影响 当代计算机技术的前沿动态 计算机在未来教育中的角色 3. 编程基础概念 变量的定义与作…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

LangFlow技术架构分析

&#x1f527; LangFlow 的可视化技术栈 前端节点编辑器 底层框架&#xff1a;基于 &#xff08;一个现代化的 React 节点绘图库&#xff09; 功能&#xff1a; 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...

13.10 LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析

LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析 LanguageMentor 对话式训练系统架构与实现 关键词:多轮对话系统设计、场景化提示工程、情感识别优化、LangGraph 状态管理、Ollama 私有化部署 1. 对话训练系统技术架构 采用四层架构实现高扩展性的对话训练…...