8.C++解構函式
解構函式
既然在建立物件時有建構函式(給成員初始化),那麼在銷燬物件時應該還有一個清除成員變數資料的操作咯。
概念
解構函式:與建構函式功能相反,解構函式不是完成物件的銷燬,區域性物件銷燬工作是由編譯器完成的。而物件在銷燬時會自動呼叫解構函式,完成類的一些資源清理工作。
特性
解構函式是特殊的成員函式
特徵如下:
- 解構函式名是~類名;
- 無引數無返回值;
- 一個類有且只有一個解構函式;
- 物件宣告週期結束,編譯器自動呼叫解構函式;
class Stack { public: Stack(int capacity = 4) : _size(0), _capacity(capacity), _p(new int[_capacity]) { cout << "Stack(int capacity = 4)" << endl; } ~Stack() { cout << "~Stack()" << endl; if (_p) { delete[](_p); _p = nullptr; } _size = _capacity = 0; } private: int _capacity; int _size; int* _p; }; int main() { Stack s; return 0;//程式結束,呼叫s的解構函式 }
輸出:
解構函式處理自定義型別
class String { public: String(const char* str = "songxin") { cout << "String(const char* str = \"songxin\")" << endl; _str = (char*)malloc(strlen(str) + 1); strcpy(_str, str); } ~String() { cout << "~String()" << endl; free(_str); _str = nullptr; } private: char* _str; }; class Person { public: Person() : _age(20), _name() { cout << "Person()" << endl; } ~Person() { cout << "~Person()" << endl; } private: String _name; int _age; }; int main() { Person p; return 0; }
輸出:
解構函式在程式即將結束時,呼叫了Person的解構函式,在Person類的解構函式即將結束接著呼叫String類的解構函式。
歸納一下:
解構函式是與建構函式執行相反的操作的,建構函式負責給物件成員變數初始化並載入資源,而解構函式則是給物件的成員變數清理資源,而不是清理物件本身。
編譯器生成的預設解構函式
編譯器預設生成的解構函式能做些什麼工作呢?我們前面已經介紹了編譯器生成的建構函式會去只會處理自定義型別的成員變數,那麼析構既然和構造相對應,析構也應該是隻去處理自定義型別的成員變數吧,確實如此,解構函式不會對內建型別有任何處理,只會在呼叫自身的析構後再去呼叫自定義型別成員的析構。
- 關於編譯器自動生成的解構函式,下面的程式我們會看到,編譯器生成的解構函式,會對自定型別成員呼叫它的解構函式。
class String
{
public:
String(const char* str = "songxin")
{
cout << "String(const char* str = \"songxin\")" << endl;
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
_str = nullptr;
}
private:
char* _str;
};
class Person
{
public:
Person()
:
_age(20),
_name()
{
cout << "Person()" << endl;
}
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
輸出:
預設生成的解構函式對成員變數的處理
- 內建型別不處理;
- 自定義型別成員呼叫相應的解構函式;
那成員變數中的內建型別處不處理其實都無所謂嘛,反正都要歸還給作業系統,但是有例外:
如果成員變數含有指標,並且指標指向一塊我們正使用的空間,指標也是內建型別,那如果不釋放指標指向的那塊空間就會造成記憶體洩漏,而編譯器生成的解構函式是不會處理此情況的,因為需要我們在解構函式中主動釋放記憶體,也就是說需要我們顯式的去定義解構函式。
class Stack
{
public:
Stack(int capacity = 4)
:
_size(0),
_capacity(capacity),
_p(new int[_capacity])//使用new去申請記憶體
{
cout << "Stack(int capacity = 4)" << endl;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_p)
{
delete[](_p);//釋放記憶體
_p = nullptr;
}
_size = _capacity = 0;
}
private:
int _capacity;
int _size;
int* _p;
};
int main()
{
Stack s;
return 0;//程式結束,呼叫s的解構函式
}
解構函式無論是我們顯式定義的還是編譯器生成的,都會在物件的宣告週期結束時自動呼叫,並且會調用自定義型別成員變數的解構函式來釋放資源,而對內建型別不做處理。
可以不顯式定義解構函式的情況
- 類的成員都是自定義型別的;
- 類的成員都是非指標的內建型別;
- 成員有指標,但並沒有管理記憶體資源;
如果類的成員變數有指標型別,並且我們讓指標指向了一塊動態分配的空間,那麼就需要我們自己寫析構函數了。
總結:不是類直接管理另一塊記憶體資源的,就不需要寫解構函式,編譯器自己生成的就能處理。