1、析构函数的概念
//析构函数:与构造函数的功能相反,析构函数不是完成对对象本身的销毁(局部对象销毁工作是由编译器完成的),
//而是完成对象中资源的清理工作(对象在销毁时会自动调用析构函数)。
2、析构函数的特性
//析构函数是特殊的成员函数,其特征如下:
//(1)析构函数名是在类名前加上字符 ~(按位取反)
//(2)无参数无返回值类型。
//(3)一个类只能有一个析构函数(无法重载)。如果我们没写析构函数,系统就会自动生成 默认析构函数。
//(4)对象生命周期结束时,编译器系统会自动调用析构函数。
//额外补充:先定义的类后析构,后定义的类先析构(与函数栈帧的开辟销毁顺序一致)
//补充:自动调用构造函数和析构函数的优势:
// Stack st;
// StackInit(&st); ------ Stack st; { 实例化对象st时,就会自动调用构造函数进行初始化,不需要再额外调用StackInit(&st)了 }
//
// bool ret = StackEmpty(&st);
// StackDestroy(&st); ---------- return st.Empty(); { 出了作用域(return之后),st自动调用析构函数进行清理,不要额外调用StackDestroy(&st)了 }
// return ret;
#include <iostream>
#include <assert.h>
using namespace std;
//日期类
class Data
{
public:
//全缺省构造函数
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
~Data() // ~Data()没有什么需要清理的,所以不需要写 (由编译器默认生成析构函数,也没清理什么)
{
cout << "~Data()" << endl;
}
private:
int _year;
int _month;
int _day;
};
//Stack类
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
assert(_array);
_capacity = capacity;
_size = 0;
}
~Stack() //~Stack()需要清理malloc出的空间,所以需要自己写 {对象中有 动态开辟的资源,最后需要手动释放的,需要自己写构造函数(因为在这种情况下默认生成的析构函数无法完成清理)}
{
free(_array);
_array = nullptr;
_capacity = _size = 0;
}
private:
DataType* _array;
int _capacity;
int _size;
};
void Func()
{
Data d;
Stack st1;
}
int main()
{
Func();
return 0;
}
3、当我们不写,编译器自动生成的默认析构函数
// 与构造函数类似:a\ 内置类型成员变量不处理(无法正确判断,无法处理。例如指针,到底是动态开辟的,还是fopen的,或者是6出来的)。 b\ 自定义类型成员变量会去调用它的析构函数。
//Stack类
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
assert(_array);
_capacity = capacity;
_size = 0;
}
~Stack()
{
free(_array);
_array = nullptr;
_capacity = _size = 0;
}
private:
DataType* _array;
int _capacity;
int _size;
};
//MyQueue类
class MyQueue
{
public:
void push(int x) {}
//...
//MyQueue类里没有写析构函数,但是MyQueue类里默认生成的析构函数会让 自定义类型成员变量 去调用它的析构函数,不处理内置类型成员变量
private:
size_t _size = 0; //内置类型成员变量不处理,但是加了缺省值
Stack _st1; //自定义类型成员变量会处理
Stack _st2;
};
void Func()
{
MyQueue q1;
}
int main()
{
Func();
return 0;
}
4、析构函数的总结(对象生命周期结束后,C++编译器会自动调用析构函数)
//分析思路:需不需要写析构函数:如果不写,系统自动生成的默认析构函数能否处理;如果默认析构函数无法处理,则就要写。
//(1)需要写(默认析构函数无法处理)。动态开辟的资源存储在类成员函数中的,如果不写析构函数,
// 默认析构函数无法对动态开辟的资源处理,就会造成内存泄露。例如:Stack\Queue\List\SeqList\BinaryTree 。
// (2) 不需要写。(默认析构函数可以处理)
//A 日期类(没有类的资源需要清理)。默认析构函数就可以处理,但是没做什么。
//B 默认生成的析构函数就够用了。默认析构函数对于Stack自定义类型成员,调用Stack析构函数。