前言
上次我们讲了构造函数,这次我们来讲析构函数,尽量用简练的语言讲完析构函数的所有知识点。
往期回顾:
详解构造函数
析构函数
一、引入
通过构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么销毁的呢?
比如在构造函数中malloc了一段内存,最后要释放的话,我们还得专门写一个Destroy函数来free它,还得记得调用这个函数。
有没有一个函数像构造函数一样编译器会自动调用来帮助我们实现清理资源的工作呢?当然有,那就是析构函数。
二、外在特征
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
外貌特征:
- 析构函数名是在类名前加上字符 ~。(如Date类对象的析构函数就叫 ~Date())
- 无参数无返回值类型。(没有返回值类型,不是返回值为空,也没有参数)
三、内在特征
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
为了验证在对象生命周期结束时,编译器会自动调用析构函数,我们可以让对象叫一声,即在析构函数中写个输出语句,只要析构函数被调用了,那就会在控制台上打印出一段话。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year=2024, int month=1, int day=24)//全缺省的构造函数
{
_year = year;
_month = month;
_day = day;
}
~Date()// 析构函数
{
//Date类没有要清理的资源,所以可以什么都不写
cout << "调用了析构函数" << endl;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 2024;
int _month = 1;
int _day = 23;
};
int main()
{
Date t1;
return 0;
}
用Date类没办法向大家清晰的展示析构函数的清理工作,这里我们使用Stack来展示析构函数是如何清理资源的。
因为Stack要用malloc在栈上开空间的,所以最后就一定要将这部分空间释放掉,这就要靠析构函数来实现了。
如此一来就保证了初始化有构造函数,清理资源有析构函数,再也不用担心最后因为没有手动调用destroy函数释放资源了,析构函数会被编译器自动调用完成这件事。
代码演示
#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int StackDataType;
class Stack {
public:
// 构造函数 - StackInit
Stack(int capacity = 4) { // 这里只需要一个capacity就够了,默认给4(利用缺省参数)
_array = (StackDataType*)malloc(sizeof(StackDateType) * capacity);
if (_array == NULL) {
cout << "Malloc Failed!" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
// 析构函数 - StackDestroy
~Stack() {
// 这里就用的上析构函数了,我们需要清理开辟的内存空间(防止内存泄漏)
free(_array); //释放内存空间
_array = nullptr; //置空
_top = _capacity = 0;
}
private:
int* _array;
size_t _top;
size_t _capacity;
};
int main(void)
{
Stack s1;
Stack s2(20); // s2 栈 初始capacity给的是20
return 0;
}
- 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
注释:
程序运行结束后输出:~Time()
在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁。main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
总结:
一、如果不自己写构造函数,让编译器自动生成,那么这个自动生成的 默认构造函数:
- 对于 “内置类型” 的成员变量:不会做初始化处理。
- 对于 “自定义类型” 的成员变量:会调用它的默认构造函数(不用参数就可以调的)初始化,如果没有默认构造函数(不用参数就可以调用的构造函数)就会报错!
二、如果我们不自己写析构函数,让编译器自动生成,那么这个 默认析构函数:
- 对于 “内置类型” 的成员变量:不作处理 (不会帮你清理的.)
- 对于 “自定义类型” 的成员变量:会调用它对应的析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
总结
- 析构函数名是在类名前加上字符 ~。(如Date类对象的析构函数就叫 ~Date())
- 无参数无返回值类型。(没有返回值类型,不是返回值为空,也没有参数)
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,==编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类