析构函数
- 1.概念与特性
- 2.工作原理
- 4.析构的顺序
如果一个类中什么成员都没有,那么该类简称为空类。而空类中其实并不是真的什么都没有,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
- 构造函数:主要完成初始化工作
- 析构函数:主要完成资源的清理工作
- 拷贝构造函数:主要用于使用同类对象初始化创建对象
- 赋值运算符重载:主要是把一个对象赋值给另一个对象
- 普通对象取地址重载
- const对象取地址重载(取地址重载很少会自己实现)
本篇文章,我们来学习析构函数。
1.概念与特性
概念:
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没的呢?
析构函数:析构函数不是完成对对象本身的销毁。在对象销毁的过程中,对象中的局部成员变量会由操作系统自动回收,而由我们动态申请的内存则不会由操作系统自动回收,编译器会调用析构函数来清理对象中所申请的资源。如果对象中没有动态申请的资源,那么析构函数实际上没有需要完成的任务,这种情况下,只需要让编译器生成一个什么都不做的隐式析构函数即可。
特性:
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数,用于在对象被销毁时,清理对象中所申请的资源
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
DataType* _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;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
}
2.工作原理
析构函数在对象消亡时被调用,以清理对象所申请的资源,那具体它在何时被调用呢?
析构函数主要在以下4种情况下会被调用:
对象生命周期结束,此时会自动调用析构函数。
#include<iostream>
using namespace std;
class Person {
public:
Person() {
cout << "调用了Person的构造函数" << endl;
}
~Person() {
cout << "调用了Person的析构函数" << endl;
}
private:
int name;
};
int main() {
Person person;
return 0;
}
运行结果如下图所示:
该运行结果说明,在对象的生命周期结束后,会自动调用对象的析构函数。
调用delete函数释放new出来的空间:由于是new出来的对象是在堆上分配空间的,即使离开了作用域,其依然存在,我们必须主动delete来释放new出来的在堆上的空间,delete函数会调用析构函数来清理对象申请的资源,否则离开了作用域后,指向该对象空间的指针就会消失,我们失去了对这片空间的控制权,别人也无法使用这片空间,这就会造成内存泄漏。
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "调用了Person的构造函数" << endl;
}
~Person() {
cout << "调用了Person的析构函数" << endl;
}
private:
int name;
};
int main() {
Person* person = new Person();
delete person;
return 0;
}
成员关系:对象car是对象person的成员,person的析构函数被调用时,对象car的析构函数也被调用。
#include <iostream>
using namespace std;
class Car {
public:
Car() {
cout << "调用了Car的构造函数" << endl;
}
~Car() {
cout << "调用了Car的析构函数" << endl;
}
private:
int name;
};
class Person {
public:
Person() {
cout << "调用了Person的构造函数" << endl;
}
~Person() {
cout << "调用了Person的析构函数" << endl;
}
private:
int name;
Car car;
};
int main() {
Person person;
return 0;
}
继承关系:当Person是Student的父类,调用Student的析构函数,会调用Person的析构函数。
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "调用了Person的构造函数" << endl;
}
~Person() {
cout << "调用了Person的析构函数" << endl;
}
private:
int name;
};
class Student :public Person {
public:
Student() {
cout << "调用了Student的构造函数" << endl;
}
~Student() {
cout << "调用了Student的析构函数" << endl;
}
private:
int name;
string no;
};
int main() {
Student student;
return 0;
}
默认析构函数是怎么工作的呢?
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
程序运行结束后输出:~Time(),在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
默认析构函数,对于内置类型成员不做处理,对于自定义类型成员会去调用它的析构函数。
总结: 默认构造函数对内置类型不做处理,对自定义类型会调用它的默认构造函数,默认析构函数对内置类型不做处理,对自定义类型会调用它的析构函数。
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
4.析构的顺序
因为局部变量是存储在栈中的,因此最后创建的对象将最先被删除,最先创建的对象最后被删除,即局部对象,构造函数和析构函数是相反的顺序,static改变对象的生存作用域,会放在局部对象之后,全局对象之前进行析构。