一、什么是析构函数?
- 析构函数是类的一种特殊成员函数,在对象销毁时自动调用,用于释放对象占用的资源(如堆内存、文件句柄、网络连接等)。
- 析构函数与构造函数相对,相当于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++规定后定义的先析构。
类类型成员会自动调用析构
- 若我们显示写了析构,对于自定义类型的成员也会调用他的析构,即自定义类型的成员无论什么时候都会自动调用析构函数。