C++析構函數:對象銷燬時的清理工作

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++中對象“生命終點”的清理工具,核心作用是:
- 釋放動態分配的內存(避免內存泄漏);
- 關閉打開的文件或網絡連接;
- 清理其他臨時資源。

記住:對象銷燬時,析構函數會自動調用,無需手動觸發。合理使用析構函數是避免資源浪費和內存泄漏的關鍵!

小夜