C++:析构函数

一、什么是析构函数?

  • 析构函数是类的一种特殊成员函数,在对象销毁时自动调用,用于释放对象占用的资源(如堆内存、文件句柄、网络连接等)。
  • 析构函数与构造函数相对,相当于C语言中我们写的free。
cpp 复制代码
class Student {
public:
    // 构造函数
    Student() { cout << "对象诞生" << endl; }
    // 析构函数
    ~Student() { cout << "对象销毁" << endl; }
};

二、语法规则

规则 说明
函数名 ~类名
返回值 无(与构造函数一样)
参数 无(不能重载,一个类只有一个析构函数)
调用 对象生命周期结束时自动调用
cpp 复制代码
class Example {
public:
    ~Example() { }   // 正确
    // ~Example(int x) { }  // 错误:不能有参数
    // void ~Example() { }    // 错误:不能有返回值
};

三、析构函数的作用

  • 清理资源,防止内存泄漏。
cpp 复制代码
class Array {
    int* data;
public:
    Array(int size) {
        data = new int[size];   // 分配堆内存
    }
    ~Array() {
        delete[] data;          // 释放内存
    }
};
其他常见用途:

关闭文件:fclose(fp)

释放互斥锁:unlock()

断开网络连接:close(socket)

四、析构函数何时被调用?

对象类型 调用时机
局部对象(栈) 离开作用域时(如函数结束)
全局/静态对象 程序结束时
堆对象(new) 手动 delete 时
成员对象 包含它的对象销毁时自动调用其析构
cpp 复制代码
void func() {
    Student s;      // 构造
}                   // 离开作用域,自动析构

int main() {
    Student* p = new Student;
    delete p;       // 必须delete,否则析构不调用 → 内存泄漏
}

五、编译器自动生成的析构函数

  • 如果不写析构函数,编译器会生成一个默认析构函数:
    • 对于内置类型成员(int, char*, 指针等):什么都不做。
    • 对于类类型成员(string, vector等):自动调用它们各自的析构函数。
cpp 复制代码
class A {
public:
    ~A() { cout << "A的析构" << endl; }
};

class B {
    int x;    // 内置类型,忽略(看编译器怎么搞)
    A a;      // 类类型,自动调用A的析构
};  // 默认析构函数会调用 a.~A()

六、什么时候必须自定义析构函数?

情况 需要自定义
有动态分配的内存(new) ✅ 必须(释放内存)
持有系统资源(文件、锁、套接字) ✅ 必须
成员对象需要特殊清理 ❌ 通常不需要(它们的析构函数会自动调用)
没有动态资源 ❌ 可以不写,用默认即可
  • 总结:如果类中申请了资源,就必须显示写析构函数,否则会造成资源泄露。
cpp 复制代码
class MyString {
    char* buf;
public:
    MyString(const char* s) {
        buf = new char[strlen(s) + 1];
        strcpy(buf, s);
    }
    ~MyString() {
        delete[] buf;   // 必须手动释放
    }
};

七、完整示例

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

class Book {
    char* title;
public:
    Book(const char* t) {
        title = new char[strlen(t) + 1];
        strcpy(title, t);
        cout << "《" << title << "》上架" << endl;
    }
    ~Book() {
        cout << "《" << title << "》下架" << endl;
        delete[] title;
    }
};

int main() {
    Book b1("C++教程");
    Book* b2 = new Book("深入C++");
    delete b2;   // 手动释放堆对象
    return 0;
}  // b1自动析构

输出:

text

《C++教程》上架

《深入C++》上架

《深入C++》下架

《C++教程》下架

八、补充

基类的析构函数应设为虚函数
  • 如果类会被继承,将析构函数声明为 virtual,否则通过基类指针删除派生类对象时,可能只调用基类析构,导致派生类资源未释放。
cpp 复制代码
class Base {
public:
    virtual ~Base() { }   // 正确做法
};
不要在析构函数中抛出异常
  • 析构函数中抛出异常可能导致程序崩溃或资源泄漏。应捕获所有异常。
cpp 复制代码
~MyClass() {
    try { /* 可能异常的清理 */ }
    catch (...) { /* 记录日志,不抛出 */ }
}
析构函数可以显式调用(不推荐)
  • obj.~MyClass(); 之后不能再使用该对象,通常只在特殊场景(如内存池)中使用。
后定义的先析构
  • 一个局部域的多个对象,C++规定后定义的先析构。
类类型成员会自动调用析构
  • 若我们显示写了析构,对于自定义类型的成员也会调用他的析构,即自定义类型的成员无论什么时候都会自动调用析构函数。
相关推荐
Hello:CodeWorld2 小时前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法
搬砖魁首4 小时前
基础能力系列 - 多线程2 - 条件变量
c++·rust·条件变量·原子类型·线程同步互斥
chase_my_dream5 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
牛油果子哥q5 小时前
【C++ STL string 】C++ STL string 终极精讲:底层原理、内存机制、全套API、深浅拷贝、易错坑点与工程实战规范
数据库·c++
凡人叶枫7 小时前
Effective C++ 条款04:确定对象被使用前已先被初始化
java·linux·开发语言·c++·嵌入式开发
不想写代码的星星7 小时前
std::move 根本不移动,就像老婆饼里没有老婆
c++
redaijufeng7 小时前
C++雾中风景7:闭包
c++·算法·风景
小欣加油8 小时前
leetcode287寻找重复数
数据结构·c++·算法·leetcode
思麟呀8 小时前
C++11 核心特性(三):强类型枚举、static_assert 与 std::tuple
开发语言·c++