【C++】什么是析构函数及应用

析构函数可以理解为 “对象的清理工”,专门负责在对象 “消失” 时收拾残局。就像你离开房间前要关灯、关门一样,对象在销毁前也可能需要释放资源(比如关闭文件、释放内存、断开连接等)。


一、什么是析构函数?

析构函数是类的一种特殊函数,名字和类名相同,前面加~,没有参数,也没有返回值。
当对象 “寿终正寝” 时(比如超出作用域、被删除),析构函数会自动调用,帮你做收尾工作。

class FileHandler {
private:
    FILE* mFile;  // 打开的文件指针(需要手动关闭)
public:
    // 构造函数:创建对象时打开文件
    FileHandler(const std::string& filename) {
        mFile = fopen(filename.c_str(), "r");  // 打开文件
        std::cout << "文件已打开" << std::endl;
    }

    // 析构函数:对象销毁时关闭文件
    ~FileHandler() {
        if (mFile != nullptr) {
            fclose(mFile);  // 关闭文件(释放资源)
            std::cout << "文件已关闭" << std::endl;
        }
    }
};

二、什么时候需要写析构函数?

当类持有 “需要手动释放的资源” 时,必须写析构函数。常见的这类资源有:

  • 动态分配的内存(用new创建的对象 / 数组)
  • 打开的文件、网络连接、数据库连接
  • 锁、句柄等系统资源

反之,如果类只持有基本类型(int、string等),不需要手动释放资源,就不用写析构函数(编译器会自动生成一个默认的,啥也不做)。

三、析构函数的用法示例

场景 1:释放动态内存(最常见)
class Student {
private:
    char* mName;  // 动态分配的字符串(需要手动释放)
public:
    // 构造函数:分配内存
    Student(const char* name) {
        mName = new char[strlen(name) + 1];  // 分配内存
        strcpy(mName, name);
        std::cout << "创建学生:" << mName << std::endl;
    }

    // 析构函数:释放内存
    ~Student() {
        delete[] mName;  // 释放动态分配的数组
        std::cout << "学生对象已销毁" << std::endl;
    }
};

int main() {
    {
        Student s("小明");  // 创建对象,构造函数调用
        // ... 使用对象
    }  // 超出作用域,析构函数自动调用,释放mName内存
    return 0;
}

运行结果:

创建学生:小明
学生对象已销毁  // 析构函数自动执行
场景 2:避免资源泄漏(对比示例)

如果不写析构函数,会发生什么?

class BadFileHandler {
private:
    FILE* mFile;
public:
    BadFileHandler(const std::string& filename) {
        mFile = fopen(filename.c_str(), "r");
    }
    // 没有析构函数!
};

int main() {
    {
        BadFileHandler f("test.txt");  // 打开文件
    }  // 对象销毁,但文件没关闭!资源泄漏!
    return 0;
}

此时文件会一直处于 “打开” 状态,直到程序结束,这在长时间运行的程序(如服务器、APP)中会导致严重问题。

四、析构函数的关键特性

自动调用:不需要手动调用,对象销毁时自动执行。
只调用一次:一个对象的生命周期中,析构函数只会被调用一次。
继承中的析构函数:如果是父类,析构函数要加virtual(虚析构函数),否则子类的析构函数可能不会被调用(参考之前的虚函数示例)。

总结

析构函数是 “清理工具”,负责释放对象持有的资源。
必须写的情况:类里有new分配的内存、打开的文件 / 连接等需要手动释放的资源。
不用写的情况:类里只有int、string(自带析构)等基础类型。


那用智能指针就不用析构函数了呗?

可以这么理解,但不完全准确。更严谨的说法是:智能指针能减少手动写析构函数的需求,但不能完全替代析构函数。

举个具体例子你就明白了:

场景 1:只用智能指针管理内存 → 确实不用写析构函数

如果类里的资源只有 “动态内存”,且全部用智能指针管理,这时不需要手动写析构函数。

#include <memory>

class Student {
private:
    // 用智能指针管理动态内存(自动释放)
    std::unique_ptr<char[]> mName;
public:
    Student(const char* name) {
        // 智能指针自动分配内存
        mName = std::make_unique<char[]>(strlen(name) + 1);
        strcpy(mName.get(), name);
    }

    // 不需要写析构函数!
    // 因为智能指针会自动释放mName的内存,编译器默认生成的析构函数足够用
};

这里智能指针已经 “包办” 了内存释放,所以不用手动写析构函数。

场景 2:有其他资源(文件、连接等)→ 仍然需要析构函数

如果类里除了内存,还有其他资源(比如打开的文件),即使有智能指针,也需要析构函数来释放这些资源。

#include <memory>
#include <cstdio>

class Student {
private:
    std::unique_ptr<char[]> mName;  // 智能指针管理内存
    FILE* mFile;                    // 其他资源(文件)
public:
    Student(const char* name, const char* filename) {
        mName = std::make_unique<char[]>(strlen(name) + 1);
        strcpy(mName.get(), name);
        mFile = fopen(filename, "r");  // 打开文件
    }

    // 必须写析构函数!释放文件资源
    ~Student() {
        if (mFile != nullptr) {
            fclose(mFile);  // 关闭文件(智能指针管不了这个)
        }
    }
};

这里智能指针只能管好mName的内存,但mFile这个文件句柄需要析构函数来释放。
核心区别:智能指针管 “内存”,析构函数管 “其他资源”

智能指针的本职工作:自动释放它所指向的动态内存(也就是用new分配的内存)。
析构函数的本职工作:释放类里所有需要手动清理的资源,包括但不限于:
文件、网络连接、数据库连接(这些智能指针管不了)
手动分配的内存(如果没用到智能指针的话)
锁、信号量等系统资源

总结
如果类里的所有动态内存都用智能指针管理,且没有其他需要手动释放的资源 → 不用写析构函数。
只要类里有智能指针管不了的资源(文件、连接等)→ 仍然需要写析构函数。

智能指针是 “内存管理小助手”,但不是 “万能管家”,析构函数在处理非内存资源时依然不可替代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梁同学与Android

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值