目录
一、什么是析构函数?
析构函数是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++中的析构函数。
需要记住:
- 析构函数是在类名前加 ~;
- 析构函数没有返回值;
- 析构函数没有参数;
- 析构函数不能重载;
- 析构函数再对象销毁时自动调用;
- 析构函数主要用于清理对象持有的资源;
- 析构函数不销毁对象本身;
- 如果类中动态申请了内存,通常需要在析构函数中释放;
- 多个局部对象的析构顺序通常和构造顺序相反。
析构函数是C++类和对象中的重要内容,和构造函数一起构成了对象的生命周期管理的基础。后面学习拷贝构造、赋值运算符重载、动态内存管理时,还会继续用到它。