析构函数详解

目录

  • 析构函数
    • 概念
    • 特性
    • 对象的销毁顺序

感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接
🐒🐒🐒 个人主页
🥸🥸🥸 C语言
🐿️🐿️🐿️ C语言例题
🐣🐣🐣 python
🐓🐓🐓 数据结构C语言
🐔🐔🐔 C++
🐿️🐿️🐿️ 文章链接目录

析构函数

概念

通过上一篇文章我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。

而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作(有点像数据结构中的Destroy函数,作用就是清理链表 树 或堆上的空间清理,如果不清理会出现内存泄漏的情况)

注意对象空间的开辟和销毁不需要我们去解决,这些都是由系统去完成的(全局 对象 静态都是系统自己去解决),而像堆上的空间就需要我们去完成了,比如malloc开辟空间的时候需要我们去完成,在最后释放的时候也是需要我们自己去free掉空间

特性

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值类型。
    具体结构如下:
class Date
{
	~Date()
	{
		;
	}
};

类名前的~在C语言中表示按位与取反,这里的取反有完全相反的意思,所以 ~放在析构函数这里就是想说明析构函数的作用和构造函数是完全不同的

特别注意析构函数是没有参数的,而构造函数是有参数的,因为构造函数要构造,传参可以初始化,而析构函数完全没必要传参,所以就没有参数

  1. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
class Date
{
public:
	Date()
	{
		_year = 1;
		
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
};
int main()
{
	Date d1;

	return 0;
}

在这里插入图片描述

  1. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

  2. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

  3. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数
    比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

由于析构函数和构造函数都是特殊的类,所以都是有this指针的

class Date
{
public:
	Date()
	{
		_year = 1;
		
	}
	~Date()
	{
		cout << this << endl;
		cout << "~Date()" << endl;
	}
	void Print()
	{
		cout << this << endl;
		cout << "Print()" << endl;
	}
private:
	int _year;
};
void func()
{
	Date d2;
}
int main()
{
	func();
	Date d1;
	d1.Print();
	return 0;
}

通过调试我们可以看到,d1和d2的地址以this指针的方式传给函数,d1和d2在生命周期结束时会调用析构函数,而析构函数里面是打印this指针
在这里插入图片描述
我们来看看下面的代码来具体理解析构函数

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
	// CheckCapacity();扩容
		_array[_size] = data;
		_size++;
	}
	//~Stack()
	//{
	//	if (_array)
	//	{
	//		free(_array);
	//		_array = NULL;
	//		_capacity = 0;
	//		_size = 0;
	//	}
	//}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

在C语言中当没有调用Destroy函数会发生内存泄漏,具体过程如下

main函数会在栈上开辟一块空间,这块空间中也包含Stack s的指针DataType* _array(只是这个指针在mian函数开辟的空间里)
在这里插入图片描述
DataType* _array中_arrray的作用是保存Stack s开辟空间的地址
在main函数执行完后,会将main函数在栈上开辟的空间都销毁,其中就包括了指针_array
由于_array是保存着Stack s开辟空间的地址,最终会因为指针_array被销毁,导致找不到Stack s开辟出的空间

在这里插入图片描述
所以没调用Destroy函数发生的后果是很严重的,并且我们经常会忘记调用Destroy函数,为了解决这个问题才有了析构函数,因为析构函数自动调用,并且编译器可以自动生成析构函数,这对我们来说是非常方便的

但是需要注意的是默认生成的析构函数和默认生成的构造函数类似,对内置类型不做处理,自定义类型的成员会去调用他的析构函数

对象的销毁顺序

生命周期对于现在学到的来说有两种,一种是局部(存在一些函数中,因为调用函数会开辟栈帧,所以函数结束后栈帧也会被销毁,函数中的局部变量也就销毁了),另一种是静态或者全局的(存在静态区里,在mian函数结束后就会销毁)

而对象生命周期结束时,C++编译系统系统自动调用析构函数,那如果有多个对象生命周期同时结束,系统会优先给谁调用析构函数

class Date
{
public:
	Date(int year)
	{
		_year = year;
}
	~Date()
	{
		cout << "~Date()" << _year<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(1);
	Date d2(2);
}

这段代码中我们只定义了一个成员变量_year,其他的_month以及_day都只是声明,不占用内存空间,mian函数中Date d1(1),Date d2(2)是对_year进行初始化,在函数结束后两个对象的生命周期都会结束,而销毁的顺序如图
在这里插入图片描述
这个调用的顺序像栈中的后进先出,Date d1先入栈,所以最后调用析构函数,事实上对象确实存储在栈上的,因为类其实是一个函数,在函数调用时会建立栈帧,所以空间存储在栈上

class Date
{
public:
	Date(int year)
	{
		_year = year;
}
	~Date()
	{
		cout << "~Date()" << _year<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(1);
	Date d2(2);
	static Date d3(3);
}

那如果让Date d3加上一个static去修饰结果会怎么样
在这里插入图片描述
加上static修饰后Date d3的存储区域就发生变化了,d3存储在一个单独的静态区中,虽然d3是一个局部变量,但是他的生命周期在经过static修饰后变成全局,所以d3会在main函数结束后销毁,而在main函数结束前会将里面的d1和d2等局部变量先销毁掉,所以d3排在最后

我们再来看看下面的代码,这段代码中定义了一个函数func,将类的对象定义在函数中,其中d4是被static修饰的,而d3没有被修饰,然后在main函数中调用func

class Date
{
public:
	Date(int year)
	{
		_year = year;
}
	~Date()
	{
		cout << "~Date()" << _year<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void func()
{
	Date d3(3);
	static Date d4(4);
}
int main()
{
	Date d1(1);
	Date d2(2);
	func();
}

销毁顺序是3 2 1 4,具体原因还是因为func也是一个函数,空间开辟在栈上的,满足后进先出原则,所以先销毁对象d3(对于d3为什么是最先销毁,可能是因为他在函数func中,算是一个局部中的局部吧),然后又是d2 d1,d4因为被static修饰,所以最后销毁
在这里插入图片描述
我们再在main函数外定义一个对象d5又会怎么样

class Date
{
public:
	Date(int year)
	{
		_year = year;
}
	~Date()
	{
		cout << "~Date()" << _year<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void func()
{
	Date d3(3);
	static Date d4(4);
}
Date d5(5);
int main()
{
	Date d1(1);
	Date d2(2);
	func();
}

结果是3 2 1 4 5
在这里插入图片描述
d5虽然没有被static修饰,但是他定义在main函数外的,所以他自己就是一个全局变量,但是这里的全局变量有两个,一个是d4,一个是d5,他们的销毁顺序是否也和自己的位置有关呢?

我们将d5的定义移到func函数上边,发现没有变化,所以推测可能是因为d4是在func函数中,所以相对于d5来讲,d4的声明周期是局部的
在这里插入图片描述

class Date
{
public:
	Date(int year)
	{
		_year = year;
}
	~Date()
	{
		cout << "~Date()" << _year<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
static Date d6(6);
Date d5(5);
void func()
{
	Date d3(3);
	static Date d4(4);
}
int main()
{
	func();
	Date d1(1);
	Date d2(2);
	

我们再在main函数外定义一个d6,用static修饰他的顺序又会怎么样
在这里插入图片描述
当d6和d5交换顺序后,发现销毁的顺序变化了,所以我们得出结论,全局的销毁顺序和局部的销毁顺序也是一样的,当d5后入栈时,d5就先销毁,而static修饰全局变量d6,并不会改变d6的销毁顺序
在这里插入图片描述
在这里插入图片描述
如果在多个函数func中定义类的顺序会怎么样

class Date
{
public:
	Date(int year)
	{
		_year = year;
}
	~Date()
	{
		cout << "~Date()" << _year<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
static Date d6(6);
Date d5(5);
void func2()
{
	Date d7(7);
	static Date d8(8);
}
void func1()
{
	Date d3(3);
	static Date d4(4);
}

int main()
{
	Date d1(1);
	Date d2(2);
	func1();
	func2();
}

在这里插入图片描述
这里说下我的想法,func1和func2因为都是在栈上开的空间,所以他们的销毁的顺序满足后进先出,具体判断谁先销毁的方法就是看谁最先被调用,也就是在main函数中去看func1是否比func2先调用,如果先比func2调用,那就说明func1先开辟空间,所以func1要比func2后销毁
在这里插入图片描述
最终顺序总结如下
局部对象(后定义先析构)->局部静态->全局对象(后定义先析构)

类中没有显示定义析构函数,系统则会自动生成默认的析构函数,那这个析构函数是否和构造函数一样基本上什么事都不做呢?
由于自定义类型的尽头是内置类型,对应类而言如果类中没有申请资源时,析构函数可以不写(因为不写不会有影响),有资源申请时,一定要写,否则会造成资源泄漏

为什么析构不可以自己去处理内置类型呢?
因为内置类型中有指针等许多不能随便处理的类型,假如指针指向了一块空间,如果析构函数可以处理内置类型的话,有可能会直接把指针指向的空间给销毁了,这样指针就变成了野指针

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开源标注工具LabelMe的使用

开源标注工具LabelMe使用Python实现&#xff0c;并使用Qt作为其图形界面&#xff0c;进行图像多边形标注。源码地址:https://github.com/labelmeai/labelme &#xff0c;最新发布版本为v5.4.1&#xff0c;它遵循GNU通用公共许可证的条款。 1.Features (1).多边形、矩形、圆形、…

Linux下mysql备份

参考文章&#xff1a; Linux实现MySQL数据库数据自动备份&#xff0c;并定期删除以前备份文件-CSDN博客文章浏览阅读7.2k次&#xff0c;点赞7次&#xff0c;收藏29次。引言在学习过程中遇到了一个问题&#xff0c;见图&#xff1a;当我进入服务器的数据库时&#xff0c;原来的…

羊大师:羊奶健康的成长伴侣

羊大师&#xff1a;羊奶健康的成长伴侣 在追求健康生活的当下&#xff0c;越来越多的人开始关注饮食的营养与健康。羊大师发现在众多天然食品中&#xff0c;羊奶以其独特的营养价值和健康益处&#xff0c;逐渐成为了人们的新宠。特别是对于正在成长发育的孩子们来说&#xff0…

客户端Web资源缓存

为了提高Web服务器的性能,其中的一种可以提高Web服务器性能的方法就是采用缓存技术。 1.缓存 1.1.什么是缓存&#xff1f; 如果某个资源的计算耗时或耗资源&#xff0c;则执行一次并存储结果。当有人随后请求该资源时&#xff0c;返回存储的结果&#xff0c;而不是再次计算。…

免费视频格式在线转换网站,推荐这5款!

在数字化时代&#xff0c;视频已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着各种设备和平台的不断涌现&#xff0c;视频格式繁多&#xff0c;常常会出现不兼容的情况。为了解决这一问题&#xff0c;视频格式在线转换网站应运而生&#xff0c;成为了我们应…

【数据结构】排序(归并排序,计数排序)

一、归并排序 基本思想&#xff1a; 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xf…

百度百舸 AIAK-LLM 的大模型训练和推理加速实践

本文整理自 4 月 16 日的 2024 百度 Create 大会的公开课分享《百舸 AIAK-LLM&#xff1a;大模型训练和推理加速实践》。 今天要分享的主题是 AI Infra 相关的内容&#xff0c;主要内容分为四部分。 首先和大家一起讨论大模型给基础设施带来的挑战。第二部分则是向大家介绍一个…

力扣HOT100 - 32. 最长有效括号

解题思路&#xff1a; 栈 class Solution {public int longestValidParentheses(String s) {int max 0;// 也可以使用 Stack<Integer> stacknew Stack<>();但Stack是遗留类&#xff0c;不推荐Deque<Integer> stack new LinkedList<>();stack.push(…

犀牛Rhinoceros 8创建、编辑、分析、记录、渲染、制作动画和转换

Rhino - 多功能 3D 建模器。 Rhinoceros 可以创建、编辑、分析、记录、渲染、制作动画和转换 NURBS* 曲线、曲面、实体、点云和多边形网格。除了硬件之外&#xff0c;复杂性、程度或大小没有任何限制。 特殊功能包括&#xff1a; -不受约束的自由形式 3D 建模工具&#xff0c;…

【汇编】算术指令

一、加法指令 &#xff08;一&#xff09;各加法指令的格式及操作 加法指令可做字或字节运算 &#xff08;1&#xff09;加法指令 ADD 格式&#xff1a;ADD DST,SRC执行的操作&#xff1a;(DST) ← (SRC)(DST) &#xff08;2&#xff09;带进位加法指令 ADC 格式&#xf…

ENZO--Leptin (human) ELISA kit

瘦素(Leptin)是由ob基因编码、在脂肪组织中生成的一种脂肪代谢调控产物&#xff0c;在代谢和调控体重等方面发挥重要作用。它通过下丘脑中的瘦素受体发出信号&#xff0c;降低食欲&#xff0c;增加能量消耗。在外周组织中&#xff0c;瘦素能拮抗胰岛素信号传导&#xff0c;增加…

目标检测标注工具Labelimg安装与使用

目录 一、安装Labelimg与打开 二、使用 1、基本功能介绍 2、快捷键 3、状态栏的工具 4、数据准备 5、标注 三、附录 1、YOLO模式创建标签的样式 2、create ML模式创建标签的样式 3、PascalVOC模式创建标签的样式 一、安装Labelimg与打开 源码网址&#xff1a;Label…

前端通知组件封装

背景 实现如上图效果&#xff1a;点击小铃铛&#xff0c;从右侧展示通知&#xff0c;点击其中一条跳&#xff0c;转到一个新页面&#xff1b;小铃铛数目减少&#xff1b; 实现 index.vue <template><el-drawerv-if"visible":visible.sync"visible&…

C#知识|上位机子窗体嵌入主窗体方法(实例)

哈喽,你好啊,我是雷工! 上位机开发中,经常会需要将子窗体嵌入到主窗体, 本节练习C#中在主窗体的某个容器中打开子窗体的方法。 01 需求说明 本节练习将【账号管理】子窗体在主窗体的panelMain容器中打开。 账号管理子窗体如下: 主窗体的panelMain容器位置如图: 02 实现…

【找到所有数组中消失的数字】leetcode,python

很菜的写法&#xff1a; class Solution:def findDisappearedNumbers(self, nums: List[int]) -> List[int]:nlen(nums)#存1-Nnum_1[i for i in range(1,n1)]#预存数num_2[]nums.sort()for i in nums:num_1[i-1]0for i in num_1:if i!0:num_2.append(i)return num_2能过但是…

计算机毕业设计hadoop+hive+hbase学情分析 在线教育大数据 课程推荐系统 机器学习 深度学习 人工智能 大数据毕业设计 知识图谱

毕 业 设 计&#xff08;论 文&#xff09;开 题 报 告 1&#xff0e;结合毕业设计&#xff08;论文&#xff09;课题情况&#xff0c;根据所查阅的文献资料&#xff0c;每人撰写不少于1000字的文献综述&#xff1a; 一、研究背景和意义 “互联网”和大数据带来了网络教育的蓬…

Java入门——异常

异常的背景 初识异常 我们曾经的代码中已经接触了一些 "异常" 了. 例如: //除以 0 System.out.println(10 / 0); // 执行结果 Exception in thread "main" java.lang.ArithmeticException: / by zero //数组下标越界 int[] arr {1, 2, 3}; System.out.…

C语言之指针初阶

目录 前言 一、内存与地址的关系 二、指针变量 三、野指针 四、const 五、传值调用与传址调用 总结 前言 本文主要介绍C语言指针的一些基础知识&#xff0c;为后面深入理解指针打下基础&#xff0c;因此本文内容主要包括内存与地址的关系&#xff0c;指针的基本语法&…

LiveGBS流媒体平台GB/T28181用户手册-服务器概览:通道信息、负载信息、CPU使用、存储使用、带宽使用(Mbps)、内存使用

LiveGBS用户手册-服务器概览&#xff1a;通道信息、负载信息、CPU使用、存储使用、带宽使用&#xff08;Mbps&#xff09;、内存使用 1、服务器概览1.1、通道信息1.2、负载信息1.2.1、信息说明1.2.2、会话列表 1.3、CPU使用1.4、存储使用1.5、带宽使用&#xff08;Mbps&#xf…

视频下载器 - 网页视频自动嗅探2.2.4

【应用名称】&#xff1a;视频下载器 - 网页视频自动嗅探 【适用平台】&#xff1a;#Android 【软件标签】&#xff1a;#Video #Downloader 【应用版本】&#xff1a;2.2.4 【应用大小】&#xff1a;33MB 【软件说明】&#xff1a;软件升级更新。支持多种格式的看片神器&am…