目录

一、析构函数的概念与作用

二、析构函数的特性

三、构造与析构顺序示例

四、编译器生成的默认析构函数

1、编译器生成的析构函数的行为

2、显式编写析构函数时的情况

3、关键点补充

五、何时需要显式定义析构函数

六、代码示例分析

Stack类实现

MyQueue类(使用默认析构)

七、C++与C实现的对比优势

C++版括号匹配(使用构造/析构)

C版括号匹配(需手动管理)


一、析构函数的概念与作用

        析构函数是与构造函数功能相反的特殊成员函数,它不负责对象本身的销毁(对象内存由系统自动回收),而是在对象生命周期结束时自动被调用,用于完成对象中资源的清理工作。

析构函数的作用类似于我们之前实现的Destroy功能。例如:

  • Stack类需要析构函数来释放动态分配的内存

  • Date类不需要显式定义析构函数,因为它没有需要手动释放的资源

        虽然局部变量会随着对象的销毁而自动销毁(例如日期类中的_year_month_day等基本类型成员),但对于管理动态资源的类(如栈、堆内存等),析构函数的作用至关重要例如:

class Stack {
public:
    Stack(int capacity = 4) {
        _array = (int*)malloc(sizeof(int) * capacity);
        // ... 其他初始化
    }
    
    ~Stack() {
        free(_array);  // 必须手动释放动态分配的内存
        _array = nullptr;
    }
private:
    int* _array;
    // ... 其他成员
};

二、析构函数的特性

  1. 命名规则:析构函数名是在类名前加上字符~

    class Date {
    public:
        Date() {}    // 构造函数
        ~Date() {}   // 析构函数
    private:
        int _year;
        int _month;
        int _day;
    };
  2. 无参数无返回值:与构造函数类似,析构函数没有参数,也没有任何返回值类型(不是void)

  3. 自动调用:对象生命周期结束时,C++编译器会自动调用析构函数

  4. 唯一性:一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认析构函数

  5. 构造与析构顺序:遵循"先构造的后析构,后构造的先析构"的原则(栈式管理)


三、构造与析构顺序示例

C++规定:局部对象后定义的先析构(栈式管理,后进先出)

int main() {
    Date d1; // 先构造,先入栈
    Date d2; // 后构造,后入栈
    return 0;
    // d2先析构(后进先出)
    // d1后析构
}


四、编译器自动生成的默认析构函数

1、编译器自动生成的析构函数的行为

  • 内置类型成员:编译器生成的默认析构函数确实不会对内置类型(如 intfloat、指针等)做任何处理。内置类型的销毁不需要额外操作,其内存会被系统自动回收。

  • 自定义类型成员:编译器会为每个自定义类型成员(如类对象、结构体等)调用其编译器自动生成的默认析构函数。这是递归进行的,确保所有嵌套的自定义类型都能正确析构。

2、显式编写析构函数时的情况

  • 如果显式编写析构函数,编译器不会生成默认析构函数,而是使用用户定义的析构函数。

  • 在用户定义的析构函数执行完毕后,编译器会自动插入代码来调用所有自定义类型成员的析构函数(按照成员声明的逆序)。内置类型成员仍然不会被处理。

  • 这一过程是隐式的,无需用户手动调用。

3、关键点补充

  • 析构函数的调用顺序:先执行用户定义的析构函数体,再隐式调用成员的析构函数(逆序)。

  • 内置类型是否需要“处理”?

    • 一般情况下,内置类型不需要显式释放(如 intfloat)。

    • 但如果内置类型是指针,且指向动态分配的内存,则需要用户在析构函数中手动释放(否则内存泄漏)。这是编译器无法自动处理的。


五、何时需要显式定义析构函数

情况 是否需要显式析构 示例
无资源申请 不需要 Date
包含自定义类型成员 视情况而定 MyQueue类(使用默认析构即可)
有资源申请 必须显式定义 Stack类(需释放动态内存)

六、代码示例分析

Stack类实现

class Stack {
public:
    Stack(int n = 4) {  // 构造函数分配资源
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (!_a) {
            perror("malloc failed");
            return;
        }
        _capacity = n;
        _top = 0;
    }
    
    ~Stack() {  // 析构函数释放资源
        cout << "~Stack()" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
    
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

MyQueue类(使用默认析构)

class MyQueue {
public:
    // 使用编译器生成的默认析构函数
    // 会自动调用pushst和popst的析构函数
private:
    Stack pushst;  // 自定义类型成员
    Stack popst;   // 自定义类型成员
};

 这种机制确保了资源管理的正确性和便利性,减少了C语言中常见的内存泄漏问题。


七、C++与C实现的对比优势

        比较C++和C语言实现的栈结构在解决括号匹配问题isValid时(在数据结构部分讲过这道题),我们发现C++的构造函数和析构函数确实带来了便利:既避免了忘记调用Init和Destroy函数的问题,也使代码更加简洁高效。

C++版括号匹配(使用构造/析构)

bool isValid(const char* s) {
    Stack st;  // 自动初始化
    // ... 使用逻辑
    return st.Empty();
    // 自动调用析构
}

C版括号匹配(需手动管理)

bool isValid(const char* s) {
    ST st;
    STInit(&st);  // 必须手动初始化
    // ... 使用逻辑
    bool ret = STEmpty(&st);
    STDestroy(&st);  // 必须手动释放
    return ret;
}

C++优势:自动资源管理,避免忘记初始化和释放,代码更简洁安全。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐