19 析构函数

析构函数也是一种特殊的成员函数,它的作用与构造函数相反,是在撤销对象占用的内存空间之前完成一些清理工作,使得这部分内存可以被程序重新分配。

1 语法

与构造函数相似,析构函数也没有函数类型,没有返回值。 与构造函数不同的是,析构函数不可以有参数,而且一个类中只能有一个析构函数,不能重载。

在类中定义析构函数的语法如下:

~类名(){
    函数体
}

2 默认析构函数

与构造函数相同,如果我们没有在类中显式定义析构函数,那么编译器会自动产生一个默认构造函数,可以认为,这是一个函数体为空的析构函数。

~类名(){}

3 析构函数的调用

从析构函数的语法,可以知道, 析构函数是没有函数名的。 这是因为, 析构函数是对象生命周期结束时,由程序自动调用的,不能被显式调用。

#include<iostream>

using namespace std;

class Student {
private:
    string name;
    int age;
    int score;
public:
    Student(string _name, int _age, int _score) : name(_name), age(_age > 0 ? _age : 0), score(_score) {
        cout << "调用" << name << "的构造函数" << endl;
    }
    ~Student(){
        cout << "调用" << name << "的析构函数" << endl;
    }
};

int main() {
    Student stu1("张三", 16, 85), stu2("李四", 18, 90);
    cout << "main函数" << endl;
    return 0;
}

程序输出:
调用张三的构造函数
调用李四的构造函数
main函数
调用李四的析构函数
调用张三的析构函数

从程序的结果,可以看出 析构函数的调用顺序与构造函数相反,先构造的对象后析构;后构造的函数先析构。

4 析构函数的使用场景

在我们之前的学习中,我们编写的类,都没有析构函数,它们运行也没有任何问题。

实际上, 一般来说,我们并不需要为类显式定义析构函数。 使用编译器自动生成的默认析构函数即可。

但是,同学们还记得 动态存储分配 吗?

通过动态存储分配获得到的变量,必须使用 delete 关键字,显式地进行销毁,这个变量所占用的内存空间才会被释放。否则,将会导致 内存泄漏 。这会使电脑运行缓慢,甚至电脑崩溃。

在类的构造函数中,我们同样可以使用 new 关键字,对类的成员数据使用动态存储分配。如果我们这样做了,那么我们就一定要在析构函数中,使用 delete 关键字将此数据释放,以避免内存泄漏。

在下面的例子中,score 属性不在只是存储一个成绩数据,而是存储多个数据,比如语文、数学、外语三门学科的成绩,组成一个数组,并使用动态存储分配。

#include<iostream>

using namespace std;

class Student {
private:
    string name;
    int age;
    // 数组指针属性
    int* score;
public:
    // 在构造函数中,初始化动态存储分配的数组
    Student(string _name, int _age, int _score[]) : name(_name), age(_age > 0 ? _age : 0) {
        score = new int[3];
        for (int i = 0; i < 3; ++i) {
            *(score + i) = _score[i];
        }
    }
    // 在析构函数中,销毁动态存储分配的数组
    ~Student(){
        delete []score;
    }
};

int main() {
    int score[3] = {98, 90, 85};
    Student stu1("张三", 16, score);
    cout << "main函数" << endl;
    return 0;
}