C++学习笔记17:析构函数

目录

一、什么是析构函数?

二、析构函数写法

三、析构函数的特点

四、析构函数什么时候调用?

五、析构函数不是销毁对象本身

六、为什么需要析构函数?

七、用析构函数释放动态内存

八、析构函数的调用顺序

九、析构函数和构造函数的区别

十、什么时候必须写析构函数?

十一、小结


一、什么是析构函数?

析构函数是C++类中的一种特殊成员函数。

它会在队形生命周期结束时自动调用,主要作用是:

清理对象在使用过程中申请的资源。

比如一个类中如果动态申请了内存,那么在对象销毁前,就应该把这块内存释放掉,避免内存泄露。

析构函数和构造函数刚好对应:

cpp 复制代码
构造函数:对象创建时自动调用,用来初始化对象
析构函数:对象销毁时自动调用,用来清理资源

二、析构函数写法

析构函数的函数名是在类名前面加 ~。

基本格式;

cpp 复制代码
~类名() {
    // 清理资源
}

例如:

cpp 复制代码
class Date {
	public:
		~Date() {
			cout << "Date destructor" << endl;
		}
}; 

这里:

cpp 复制代码
~Date()

就是 Date 的析构函数。


三、析构函数的特点

析构函数有几个重要的特点:

cpp 复制代码
1. 函数名是在类名前加 ~;
2. 没有返回值;
3. 没有参数;
4. 不能重载;
5. 对象销毁时自动调用。

因为析构函数没有参数,所以一个类中只能有一个析构函数,不能像构造函数那样重载。


四、析构函数什么时候调用?

当对象生命周期结束时,析构函数会自动调用。

例如局部对象离开作用域时:

cpp 复制代码
#include <iostream>
using namespace std;

class Date {
	public:
		Date() {
			cout << "Date constructor" << endl;
		}
		
		~Date() {
			cout << "Date destructor" << endl;
		}
};

int main() {
	Date d;
	
	return 0;
}

运行结果:

cpp 复制代码
Date constructor
Date destructor

当执行:

cpp 复制代码
Date d;

时,构造函数自动调用。

当 main 函数结束,对象 d 生命周期结束时,析构函数自动调用。


五、析构函数不是销毁对象本身

这一点很重要。

析构函数并不是负责把对象本是谁呢销毁。

对象空间的创建和释放由编译器管理,析构函数主要负责清理对象内部申请的资源。

比如:

cpp 复制代码
class Stack {
	private:
		int* _array;
};

如果 _array 指向的是动态申请的空间,那么对象销毁前,就应该释放这块空间。

所以析构函数更准确的作用是:

清理对象持有的资源,而不是销毁对象本身。


六、为什么需要析构函数?

假设我们写一个栈类,在初始化时动态申请空间:

cpp 复制代码
class Stack {
	public:
		void Init(int capacity) {
			_array = (int*)malloc(sizeof(int) * capacity);
			_top = 0;
			_capacity = capacity;
		}
	
	private:
		int* _array;
		int _top;
		int _capacity; 
};

如果对象用完后不是放 _array 指向的空间,就会造成内存泄漏。

在C语言中,我们通常要手动写 Destory 函数:

cpp 复制代码
void Destory() {
	free(_array);
	_array = nullptr;
	_top = 0;
	_capacity = 0;
}

但是这样有一个问题:

cpp 复制代码
如果忘记手动调用 Destory,就可能导致内存泄漏。

C++中可以把资源清理工作放到析构函数里,让对象销毁时自动完成清理。


七、用析构函数释放动态内存

示例:

cpp 复制代码
#include <iostream>
using namespace std;

class Stack {
	public:
		Stack(int capacity = 4) {
			_array = (int*)malloc(sizeof(int) * capacity);
			_top = 0;
			_capacity = capacity;
		}
		
		~Stack() {
			if (_array != nullptr) {
				free(_array);
				_array = nullptr;
			}
			
			_top = 0;
			_capacity = 0;
			
			cout << "Stack destructor" << endl;
		}
	
	private:
		int* _array;
		int _top;
		int _capacity; 
};

int main() {
	Stack s;
	
	return 0;
} 

当 s 生命周期结束时,析构函数会自动调用,释放 _array 指向的动态空间。

这样就不用手动调用 Destory 了。


八、析构函数的调用顺序

如果在同一个作用域中创建多个对象,析构函数通常和构造函数相反。

例如:

cpp 复制代码
#include <iostream>
using namespace std;

class Date {
	public:
		Date(int id) {
			_id = id;
			cout << "Date " << _id << " constructor" << endl;
		}
		
		~Date() {
			cout << "Date " << _id << " destructor" << endl;
		}
	
	private:
		int _id;
};

int main() {
	Date d1(1);
	Date d2(2);
	
	return 0;
}

运行结果:

cpp 复制代码
Date 1 constructor
Date 2 constructor
Date 2 destructor
Date 1 destructor

可以看到:

cpp 复制代码
先构造 d1,再构造 d2;
先析构 d2,再析构 d1。

这符合栈的特点,后创建的对象先销毁。


九、析构函数和构造函数的区别

对比项 构造函数 析构函数
调用时机 对象创建时 对象销毁时
主要作用 初始化对象 清理资源
函数名 与类名相同 类名前加 ~
返回值 没有返回值 没有返回值
参数 可以有参数 不能有参数
是否能重载 可以重载 不能重载
调用方式 自动调用 自动调用

构造函数和析构函数共同管理对象的生命周期。


十、什么时候必须写析构函数?

如果类中没有申请资源,一般可以不写析构函数,让编译器自动生成即可。

例如:

cpp 复制代码
class Date {
    private:
        int _year;
        int _month;
        int _day;
};

这种类只有普通成员变量,不涉及动态资源,通常不需要自己写析构函数。

但是类中有:

cpp 复制代码
动态申请的内存
打开的文件
网络连接
系统资源

就应该考虑写析构函数进行释放。

比如:

cpp 复制代码
int* _array;

如果它指向 malloc 或 new 申请的空间,就需要在析构函数中释放。


十一、小结

本篇主要学习了C++中的析构函数。

需要记住:

  1. 析构函数是在类名前加 ~;
  2. 析构函数没有返回值;
  3. 析构函数没有参数;
  4. 析构函数不能重载;
  5. 析构函数再对象销毁时自动调用;
  6. 析构函数主要用于清理对象持有的资源;
  7. 析构函数不销毁对象本身;
  8. 如果类中动态申请了内存,通常需要在析构函数中释放;
  9. 多个局部对象的析构顺序通常和构造顺序相反。

析构函数是C++类和对象中的重要内容,和构造函数一起构成了对象的生命周期管理的基础。后面学习拷贝构造、赋值运算符重载、动态内存管理时,还会继续用到它。

相关推荐
胖大和尚1 小时前
C++ 多线程编程的实现方式
c++·thread
在水一缸2 小时前
深入浅出 Catch2:现代 C++ 测试框架的优雅实践
开发语言·c++·单元测试·log4j·测试框架·catch2
2401_841495642 小时前
【数据结构】B*树
数据结构·c++·b树·算法·删除·插入·三分分裂
斐夷所非3 小时前
在纷繁竞逐中稳步前行:C++ 2006–2020
c++
保持清醒5403 小时前
类和对象上
c++
乱七八糟的屋子3 小时前
Poco C++高级实战教程:线程池+日志系统+加密算法+WebSocket长连接
c++·websocket·加密解密·#poco·#c++高级开发
hold?fish:palm4 小时前
7 接雨水
开发语言·c++·leetcode
流浪0015 小时前
数据结构篇(五):线性表——栈
数据结构·c++·算法
郝学胜-神的一滴5 小时前
中级OpenGL教程 022:探秘三维世界的血脉传承——物体父子关系与矩阵递归奥义
c++·线性代数·unity·矩阵·游戏引擎·unreal engine·opengl
ch0sen1pm5 小时前
刚学完 CAS 原子操作,我花了一晚上写了三个无锁队列
c++