1. 什麼是析構函數?¶
想象你用C++創建了一個對象,就像買了一個杯子。當杯子用完後,你需要清洗它並放回原處,對吧?在C++中,對象銷燬時也需要做“清理”工作,這就是析構函數的作用。
簡單來說:
- 構造函數是創建對象時自動調用的函數,負責初始化對象。
- 析構函數是銷燬對象時自動調用的函數,負責清理對象使用的資源(比如動態分配的內存、打開的文件等)。
2. 析構函數的定義格式¶
析構函數的語法很特殊,需要記住以下幾點:
- 名字必須與類名相同,但開頭多一個波浪線 ~(發音“tilde”)。
- 沒有參數,也沒有返回值類型(連 void 都不能寫)。
- 一個類只能有一個析構函數(不能重載)。
示例:
class MyClass {
public:
// 構造函數(創建對象時調用)
MyClass() {
cout << "對象創建了!" << endl;
}
// 析構函數(銷燬對象時調用)
~MyClass() {
cout << "對象銷燬了,資源已清理!" << endl;
}
};
3. 析構函數的核心作用:清理資源¶
爲什麼需要析構函數?最常見的原因是避免資源泄漏。比如:
- 如果對象用 new 動態分配了內存(int* p = new int;),如果不主動釋放,內存會一直被佔用,導致“內存泄漏”。
- 如果對象打開了文件(如 fstream file("test.txt");),如果不關閉文件,可能導致文件資源浪費或數據丟失。
示例:動態內存清理
class Array {
private:
int* data; // 動態數組指針
int size; // 數組大小
public:
Array(int s) : size(s) {
data = new int[size]; // 構造時分配內存
cout << "構造函數:分配了" << size << "個int的內存" << endl;
}
~Array() {
delete[] data; // 析構時釋放內存,避免泄漏!
cout << "析構函數:釋放了數組內存" << endl;
}
};
int main() {
Array arr(5); // 創建對象,調用構造函數
// arr的作用域僅在main函數內,離開main時自動銷燬,調用析構函數
return 0;
}
輸出結果:
構造函數:分配了5個int的內存
析構函數:釋放了數組內存
4. 析構函數何時被調用?¶
C++會在以下場景自動調用析構函數,無需手動觸發:
- 對象離開作用域:比如函數內的局部對象,函數結束時會調用析構函數。
void test() {
MyClass obj; // 函數內定義的局部對象
// 函數結束時,obj被銷燬,調用析構函數
}
int main() {
test(); // 函數test執行完畢,obj的析構函數被調用
return 0;
}
- 用
delete銷燬動態對象:如果對象是用new創建的(即指針指向的對象),用delete時會調用析構函數。
int main() {
MyClass* p = new MyClass(); // 動態創建對象
delete p; // 銷燬對象,調用析構函數
return 0;
}
- 臨時對象的銷燬:函數參數或返回值中的臨時對象,在表達式結束時會被銷燬。
MyClass createObj() {
return MyClass(); // 創建臨時對象並返回,函數結束時臨時對象銷燬
}
int main() {
createObj(); // 臨時對象在表達式結束時被銷燬,調用析構函數
return 0;
}
5. 默認析構函數 vs 自定義析構函數¶
如果類中沒有寫析構函數,編譯器會自動生成一個默認析構函數。默認析構函數什麼都不做(不會主動清理資源),但會自動調用成員對象的析構函數(如果有)。
示例:默認析構函數
class Inner {
public:
~Inner() {
cout << "Inner類對象被銷燬了" << endl;
}
};
class Outer {
private:
Inner inner; // Outer類的成員對象
public:
Outer() {
cout << "Outer類對象創建了" << endl;
}
// 沒有寫Outer的析構函數,編譯器會生成默認析構函數
};
int main() {
Outer outer; // 創建Outer對象
return 0; // Outer對象離開作用域,默認析構函數被調用,同時調用Inner的析構函數
}
輸出結果:
Outer類對象創建了
Inner類對象被銷燬了
6. 注意事項¶
- 不能顯式調用析構函數:C++不允許手動調用析構函數(如
obj.~MyClass();),否則會導致重複調用,引發未定義行爲。 - 不能重載析構函數:因爲沒有參數,無法通過參數區分不同的“初始化方式”,所以析構函數只有一個。
- 虛析構函數的重要性:如果基類指針指向派生類對象,且基類析構函數不是虛函數,
delete基類指針時可能只調用基類析構函數,導致派生類資源無法清理。此時需將基類析構函數聲明爲virtual(虛析構函數)。
示例:虛析構函數
class Base {
public:
virtual ~Base() { // 虛析構函數
cout << "Base析構函數調用" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived析構函數調用" << endl;
}
};
int main() {
Base* p = new Derived(); // 基類指針指向派生類對象
delete p; // 正確調用:先Derived析構,再Base析構
return 0;
}
輸出結果:
Derived析構函數調用
Base析構函數調用
總結¶
析構函數是C++中對象“生命終點”的清理工具,核心作用是:
- 釋放動態分配的內存(避免內存泄漏);
- 關閉打開的文件或網絡連接;
- 清理其他臨時資源。
記住:對象銷燬時,析構函數會自動調用,無需手動觸發。合理使用析構函數是避免資源浪費和內存泄漏的關鍵!