C++学习——如何析构派生类
C++——继承关系中的虚函数
- 析构派生类
- 纯虚构函数和抽象类
析构派生类
先看一段简单的代码:
#include <iostream>using namespace std;class AA
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() {cout << "调用了基类 func()" << endl;}~AA() {cout << "调用了基类析构!" << endl;}};class BB : public AA
{
public:BB() {cout << "调用了子类构造" << endl;}void func() {cout << "调用了子类 func()" << endl;}~BB() {cout << "调用了子类析构!" << endl;}};int main() {// AA aa;BB bb;return 0;
}
编译运行的结果如下:
调用了基类构造
调用了子类构造
调用了子类析构!
调用了基类析构!
修改一下main()函数:
int main() {BB* bb_ptr = new BB;delete bb_ptr;return 0;
}
编译运行的结果与之前一样:
调用了基类构造
调用了子类构造
调用了子类析构!
调用了基类析构!
再修改一下代码,手工调用子类的析构函数:
int main() {BB* bb_ptr = new BB;bb_ptr->~BB();bb_ptr->~BB();bb_ptr->~BB();delete bb_ptr;return 0;
}
编译运行的结果如下:
调用了基类构造
调用了子类构造
调用了子类析构!
调用了基类析构!
调用了子类析构!
调用了基类析构!
调用了子类析构!
调用了基类析构!
调用了子类析构!
调用了基类析构!
可以发现,每次手工调用子类的析构函数,都会调用一次基类的析构函数。
子类的析构函数在执行完成后,会自动调用基类的析构函数。这是C++编译器强制的规定。
继续修改代码,用基类指针指向派生类new出来的对象,代码如下:
int main() {AA* aa_ptr = new BB; // 基类指针指向派生类对象delete aa_ptr; // 基类指针释放派生类对象return 0;
}
编译运行的结果如下:
调用了基类构造
调用了子类构造
调用了基类析构!
用基类指针指向派生类对象是多态的精髓。但用基类指针销毁派生对象时,不能调用派生类的析构函数。在应用开发中,一般会把释放资源的代码写在析构函数中。如果析构函数未调用,会有内存泄漏的风险。
但上面这段代码运行的效果没有调用派生类的析构函数,如果需要调用派生类的虚构函数,则把基类的析构函数声明为虚构函数即可,代码如下:
#include <iostream>using namespace std;class AA
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() {cout << "调用了基类 func()" << endl;}// 基类的析构函数是虚函数virtual ~AA() {cout << "调用了基类析构!" << endl;}};class BB : public AA
{
public:BB() {cout << "调用了子类构造" << endl;}void func() {cout << "调用了子类 func()" << endl;}~BB() {cout << "调用了子类析构!" << endl;}};int main() {AA* aa_ptr = new BB; // 基类指针指向派生类对象delete aa_ptr; // 基类指针释放派生类对象return 0;
}
编译运行的结果如下:
调用了基类构造
调用了子类构造
调用了子类析构!
调用了基类析构!
正常情况下,释放堆区内存后,会把指针指向空,防止操作野指针。delete空指针是安全的,delete野指针会导致程序的崩溃。
delete ptr;
ptr = nullptr;
如果析构函数被调用了多次,如果没有ptr = nullptr;,那么delete ptr;操作的就是野指针。
所以,在析构函数中,释放堆区内存后,也应该把指针指向空。
对于基类,即使不需要析构函数,也应该提供一个空虚析构函数。目的是为了让派生类有机会可以重写析构函数。
下面的代码就演示了,基类没有析构函数,派生类有析构函数的情况:
#include <iostream>using namespace std;class AA
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() {cout << "调用了基类 func()" << endl;}// 基类缺少析构函数// virtual ~AA() {cout << "调用了基类析构!" << endl;} };class BB : public AA
{
public:BB() {cout << "调用了子类构造" << endl;}void func() {cout << "调用了子类 func()" << endl;}~BB() {cout << "调用了子类析构!" << endl;}};int main() {AA* aa_ptr = new BB; // 基类指针指向派生类对象delete aa_ptr; // 基类指针释放派生类对象return 0;
}
编译运行结果如下:
调用了基类构造
调用了子类构造
可以发现,销毁派生类对象时,没有调用析构函数,因为基类指针不能调用派生类的成员函数。若想调用派生类的构造函数,在基类中添加一个空的虚构函数即可。代码如下:
class AA
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() {cout << "调用了基类 func()" << endl;}// virtual ~AA() {cout << "调用了基类析构!" << endl;}virtual ~AA() {} // 空的析构函数
};
编译运行的结果如下:
调用了基类构造
调用了子类构造
调用了子类析构!
纯虚构函数和抽象类
纯虚函数是一种特殊的虚函数,在某些情况下,基类中不能对虚函数给出有意义的实现,把它声明为纯虚函数。
纯虚函数只有函数名、参数和返回值类型,没有函数体,具体实现留给该派生类去做。
语法:virtual 返回值类型 函数名 (参数列表)=0;
纯虚函数在基类中为派生类保留一个函数的名字,以便派生类它进行重定义。如果在基类中没有保留函数名字,则无法支持多态性。
含有纯虚函数的类被称为抽象类,不能实例化对象,可以创建指针和引用。
派生类必须重定义抽象类中的纯虚函数,否则也属于抽象类。
比较好理解,直接看代码:
#include <iostream>using namespace std;class AA // 含有纯虚函数的类——抽象类
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() = 0; // 纯虚函数virtual ~AA() {cout << "调用了基类析构!" << endl;}
};class BB : public AA // 派生类
{
public:BB() {cout << "调用了子类构造" << endl;}void func() {cout << "调用了子类 func()" << endl;}~BB() {cout << "调用了子类析构!" << endl;}
};int main() {BB bb; // 创建派生类对象AA* aa_ptr = &bb; // 创建抽象类指针,指向子类对象。aa_ptr->func(); // 调用子类 func()// AA& aa_ref = bb; // 创建抽象类引用,指向子类对象。// aa_ref.func(); // 调用子类 func()return 0;
}
编译运行的效果如下:
调用了基类构造
调用了子类构造
调用了子类 func()
调用了子类析构!
调用了基类析构!
基类中的纯虚析构函数也需要实现。
class AA // 抽象类
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() = 0;virtual ~AA() = 0;
};
这段代码编译会通过,但是链接过程中会报错。原因是:当派生类对象被销毁时,会调用派生类的析构函数,接着自动调用基类的析构函数。现在基类的析构函数没有代码实现了,所以报错了。这种错误是链接阶段的,不是编译阶段的。
既然纯虚析构函数一定要有代码实现,那它还有啥意义?
有时候,想使一个类成为抽象类,但刚好又没有任何纯虚函数,怎么办?
方法很简单:在想要成为抽象类的类中声明一个纯虚析构函数。
纯虚析构函数的定义要在类的外部
class AA // 抽象类
{
public:AA() {cout << "调用了基类构造" << endl;}virtual void func() = 0;virtual ~AA() = 0; // 纯虚析构函数声明
};AA::~AA() {cout << "调用了基类析构!" << endl;}; // 纯虚析构函数定义
感谢浏览,一起学习!
相关文章:
C++学习——如何析构派生类
C——继承关系中的虚函数 析构派生类纯虚构函数和抽象类 析构派生类 先看一段简单的代码: #include <iostream>using namespace std;class AA { public:AA() {cout << "调用了基类构造" << endl;}virtual void func() {cout <<…...
SpringCloud与Dubbo的区别
在构建分布式系统时,SpringCloud和Dubbo是两个常用的框架。虽然它们都能帮助开发者实现服务之间的通信和治理,但在设计理念、使用场景和技术实现上,两者存在明显的区别。本文将详细探讨SpringCloud与Dubbo的不同之处,以帮助开发者…...
C# 设计模式--建造者模式 (Builder Pattern)
定义 建造者模式是一种创建型设计模式,它允许你逐步构建复杂对象,而无需使用多个构造函数或重载。建造者模式将对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。 正确写法 假设我们有一个复杂的 Car 对象,需要…...
leetcode 23. 合并 K 个升序链表
给你一个链表数组,每个链表都已经按升序排列。 输入:lists [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [1->4->5,1->3->4,2->6 ] 将它们合并到一个有序链表中得到。 1->…...
【Redis】深入解析Redis缓存机制:全面掌握缓存更新、穿透、雪崩与击穿的终极指南
文章目录 一、Redis缓存机制概述1.1 Redis缓存的基本原理1.2 常见的Redis缓存应用场景 二、缓存更新机制2.1 缓存更新的策略2.2 示例代码:主动更新缓存 三、缓存穿透3.1 缓存穿透的原因3.2 缓解缓存穿透的方法3.3 示例代码:使用布隆过滤器 四、缓存雪崩4…...
SQL语法——DQL查询
1.查询: 基础查询: select 列名1,列名2 from 表名; # 输入列名为*时为全查 条件查询: select 列名 from 表名 where 条件; #条件中含字符串时为字符串...
云计算.运维.面试题
1、计算机能直接识别的语言( C )。 A、汇编语言 B、自然语言 C、机器语言 D、高级语言 2、应用软件是指( D )。 A、所有能够使用的软件 B、能被各应用单位共同使用的某种软件 C、所有计算机上都应使用的基本软件D、专门为某一应用目的而编制的软件 3、计算机的显示器是一…...
基于vue和vite的计算器
实现思路:1.撰写方案三次迭代(得到方案、项目结构、提问的prompt) 2. 功能实现 3. 优化迭代 计算器项目方案设计(阶段一) 一、项目基本信息 项目名称:基于 Vue 和 Vite 的计算器项目 技术栈: 前…...
《OpenCV:视觉世界的魔法钥匙》
《OpenCV:视觉世界的魔法钥匙》 一、OpenCV 是什么1. 起源与发展支持2. 特点与优势3. 编程语言支持 二、OpenCV 的发展历程1. 重要版本发布时间线2. 版本更新内容 三、OpenCV 的主要功能1. 图像处理2. 特征提取3. 目标检测4. 运动分析5. 人脸识别6. 其他功能 四、Op…...
部署kafka并通过python操作
目录 一、安装JDK1.81、检查服务器是否已安装JDK2、若已安装JDK,进行卸载3、更新yum源4、搜索JDK1.8安装包5、安装JDK1.86、查看是否安装成功7、配置环境变量 二、安装Kafka1、下载并解压kafka部署包至/usr/local/目录2、修改server.properties3、修改/etc/profile4…...
【JAVA】Java高级:数据库监控与调优:SQL调优与执行计划的分析
作为Java开发工程师,理解SQL调优和执行计划的分析是至关重要的。这不仅可以帮助我们提高数据库查询的效率,还能减少系统资源的消耗,提升整体应用的性能。 1. SQL调优的重要性 随着数据量的增加和用户请求的增多,数据库的性能问题…...
【单片机开发】MCU三种启动方式(Boot选择)[主Flash/系统存储器(BootLoader)/嵌入式SRAM]
目录 参考资料: 利用 Boot 选择不同的启动方式: 单片机的存储结构(主 FLASH/系统存储器/嵌入式 SRAM): 1. Cortex-M 内核芯片——启动原理: 1.1. 启动流程: 1.2. 根据单片机的存储器映射和架构图:启动…...
跨库移植 SQL
背景 应用程序可能要基于不同数据库工作,各种数据库的 SQL 语法大体一致,但仍有些差别,结果就要改造这些 SQL,而这事通常只能手工调整,工作量大还容易出错。 完全自动改造 SQL 几乎是无法做到的,毕竟各种…...
(软件测试文档大全)测试计划,测试报告,测试方案,压力测试报告,性能测试,等保测评,安全扫描测试,日常运维检查测试,功能测试等全下载
1. 引言 1.1. 编写目的 1.2. 项目背景 1.3. 读者对象 1.4. 参考资料 1.5. 术语与缩略语 2. 测试策略 2.1. 测试完成标准 2.2. 测试类型 2.2.1. 功能测试 2.2.2. 性能测试 2.2.3. 安全性与访问控制测试 2.3. 测试工具 3. 测试技术 4. 测试资源 4.1. 人员安排 4.2. 测试环境 4.2.…...
Vue前端开发-路由跳转及带参数跳转
在Vue 3中,由于没有实例化对象this,因此,无法通过this去访问 $route对象,而是通过导入一个名为 useRouter 的方法,执行这个方法后,返回一个路由对象,通过这个路由对象就可以获取到当前路由中的信…...
服务器上安装 Node.js
在服务器上安装 Node.js 的过程根据你使用的操作系统和环境可能会有所不同。以下是一些常见的 Linux 发行版(如 Ubuntu 或 CentOS)上的安装步骤。 在基于 Red Hat/CentOS 的系统上安装 Node.js 设置 EPEL 仓库 如果没有启用 EPEL (Extra Packages for E…...
在阿里云/Linux环境搭建Gitblit服务
在阿里云/Linux环境搭建Gitblit服务 1. 整体描述2. 前期准备3. 安装步骤3.1 下载gitblit3.2 上传gitblit3.3 解压文件3.4 修改文件配置3.5 启动gitblit3.6 安全组配置 4. 总结 1. 整体描述 前段时间买了一个阿里云服务器,2核2G,3M固定带宽的配置&#x…...
MicroBlaze软核开发(二):GPIO
实现功能:使用 MicroBlaze软核,配置GPIO用拨码开关控制LED灯 Vivado版本:2018.3 目录 引言 vivado部分: 一、配置GPIO 二、生成HDL文件编译 SDK部分: 一、导出硬件启动SDK 二、新建应用程序工程 三、编写程序代…...
threejs相机辅助对象cameraHelper
为指定相机创建一个辅助对象,显示这个相机的视锥。 想要在场景里面显示相机的视锥,需要创建两个相机。 举个例子,场景中有个相机A,想要显示相机A的视锥,那么需要一个相机B,把B放在A的后面,两个…...
Luma 视频生成 API 对接说明
Luma 视频生成 API 对接说明 随着 AI 的应用变广,各类 AI 程序已逐渐普及。AI 已逐渐深入到人们的工作生活方方面面。而 AI 涉及的行业也越来越多,从最初的写作,到医疗教育,再到现在的视频。 Luma 是一个专业高质量的视频生成平…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
