1. 析构函数的作用

析构函数是一种特殊的成员函数,它在对象生命周期结束时自动调用,主要用来:

  • 释放对象在生命周期内申请的资源(如动态内存、文件句柄、网络连接、互斥锁等)
  • 执行一些清理工作,确保资源被正确释放,防止内存泄漏

2. 析构函数的特点

  • 函数名:在类名前加 ~(波浪号)
  • 没有参数,也没有返回值类型(连 void 都不能写)(重点要记住,没有形参,没有返回值)
  • 不能重载(一个类只能有一个析构函数)(注意和构造函数可以存在多个和重载的区别)
  • 声明为 public(通常情况下),以便编译器能在对象销毁时自动调用
  • 如果用户没有定义析构函数,编译器会自动生成一个默认析构函数(空实现)

3. 基本语法示例

cpp

运行

#include <iostream>
using namespace std;

class Person {
public:
    string name;

    // 构造函数
    Person(string n) {
        name = n;
        cout << "构造函数: " << name << " 被创建\n";
    }

    // 析构函数
    ~Person() {
        cout << "析构函数: " << name << " 被销毁\n";
    }
};

int main() {
    Person p1("Alice"); // 创建对象,调用构造函数
    {
        Person p2("Bob"); // 作用域内创建
    } // p2 生命周期结束,调用析构函数
    cout << "main 函数即将结束\n";
} // p1 生命周期结束,调用析构函数

运行结果:

plaintext

构造函数: Alice 被创建
构造函数: Bob 被创建
析构函数: Bob 被销毁
main 函数即将结束
析构函数: Alice 被销毁

4. 析构函数的调用时机

析构函数自动调用,调用时机取决于对象的存储方式:

这句话又该怎么理解

4.1 栈上的局部对象

  • 当对象所在的作用域结束时,自动调用析构函数

cpp运行

void func() {
    Person p("Tom");
} // 离开 func() 时,p 的析构函数被调用

4.2 堆上的动态对象

  • 使用 new 创建的对象,必须用 delete 手动释放,delete 会调用析构函数

cpp运行

Person* p = new Person("Jerry");
delete p; // 必须手动释放,否则会内存泄漏

4.3 静态对象

  • 在程序结束时,才调用析构函数

cpp运行

void func() {
    static Person p("Static");
} // 函数结束时不会销毁,直到程序退出时才调用析构函数

5. 析构函数与资源管理(RAII)

析构函数是 RAII(资源获取即初始化) 编程思想的核心:

  • 在构造函数中获取资源
  • 在析构函数中释放资源
  • 这样可以保证资源一定会被释放,即使发生异常

cpp

运行

#include <iostream>
using namespace std;

class FileHandler {
private:
    FILE* fp;
public:
    FileHandler(const char* filename) {
        fp = fopen(filename, "w");
        if (!fp) cout << "文件打开失败\n";
        else cout << "文件已打开\n";
    }

    ~FileHandler() {
        if (fp) {
            fclose(fp);
            cout << "文件已关闭\n";
        }
    }
};

int main() {
    FileHandler fh("test.txt");
    // ... 使用文件
} // 自动调用析构函数关闭文件

6. 虚析构函数(Virtual Destructor)

当使用基类指针指向派生类对象时,如果基类析构函数不是虚函数,delete 基类指针时只会调用基类析构函数,不会调用派生类析构函数,造成资源泄漏

cpp

运行

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base 构造\n"; }
    virtual ~Base() { cout << "Base 析构\n"; } // 虚析构函数
};

class Derived : public Base {
public:
    Derived() { cout << "Derived 构造\n"; }
    ~Derived() { cout << "Derived 析构\n"; }
};

int main() {
    Base* p = new Derived(); // 基类指针指向派生类对象
    delete p; // 如果基类析构是虚函数,会先调用 Derived 析构,再调用 Base 析构
}

运行结果(虚析构):

plaintext

Base 构造
Derived 构造
Derived 析构
Base 析构

结论

  • 如果类可能被继承,且可能通过基类指针删除派生类对象,基类的析构函数应该声明为 virtual

7. 析构函数的调用顺序

  • 对于局部对象:按构造的相反顺序调用析构函数(后进先出)
  • 对于成员对象:先构造成员对象,再构造自身;析构时相反
  • 对于继承:先调用派生类析构,再调用基类析构

8. 总结要点

  • 析构函数用于对象销毁时的资源清理
  • 函数名是 ~类名,无参数,无返回值,不能重载
  • 自动调用(栈对象)或配合 delete 手动调用(堆对象)
  • 基类作为多态基类时,应声明虚析构函数
  • 是 RAII 编程思想的核心机制
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐