C++ 析構函數
設計一個類時,如何寫析構函數?
析構函數如果我們不寫的話,C++ 會幫我們自動的合成一個,就是說:C++ 會自動的幫我們寫一個析構函數。
很多時候,自動生成的析構函數可以很好的工作,但是一些重要的事蹟,就必須我們自己去寫析構函數。
析構函數和構造函數是一對。構造函數用於創建對象,而析構函數是用來撤銷對象。簡單的說:一個對象出生的時候,使用構造函數,死掉的時候,使用析構函數。
下面我們來做一個例子,看看:
#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
NoName():pstring(new std::string), i(0), d(0){}
private:
std::string * pstring;
int i;
double d;
};
int main(){
return 0;
}
像上面這個 NoName
類這樣的設計,類裏面有一個成員變量是指針(std::string *pstring
) ,那麼在構造函數裏我們使用 new
創建了對象,並使用 pstring
來操作這個對象。那麼在這個情況下,我們就必須設計一個析構函數。
析構函數是這樣編寫的:(可以在類的裏面聲明,定義寫在類的外面,)
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "構造函數被調用了!" << endl;
}
~NoName();
NoName::~NoName(){
cout << "析構函數被調用了!" << endl;
}
析構函數是這樣寫的: ~NoName()
,它與構造函數唯一的區別就是,前面都加了一個 ~
符號。 析構函數都是沒有參數的,也就是說:析構函數永遠只能寫一個。
按照 C++ 的要求,只要有 new
就要有相應的 delete
。這個 new
是在構造函數裏 new
的,就是出生的時候。所以在死掉的時候,就是調用析構函數時,我們必須對指針進行 delete
操作。
我們來在main()
函數中創建一個 NoName
實例對象來 測試一下:
int main(){
NoName a;
return 0;
}
並且在 main()
函數的 }
這一行添加一個斷點,如圖所示:
運行輸出:
構造函數被調用了!
析構函數被調用了!
在定義 a
對象的時候,調用了 NoName
類的構造函數,在main()
函數執行完的時候,就是執行完 return 0;
這句話後,main()
函數的執行域結束,所以就要殺掉 a
對象,所以這個時候會調用 NoName
類的析構函數。
那麼,如果我們在 main()
函數中使用 new
關鍵字來創建一個 NoName
類的實例化對象,會出現什麼樣的運行效果呢?
main()
函數中的代碼如下:
int main(){
NoName a;
NoName *p = new NoName;
return 0;
}
運行輸出:
構造函數被調用了!
構造函數被調用了!
析構函數被調用了!
這裏使用 new
創建的對象,就必須要使用 delete
來釋放它。 牢牢的記住:new
和 delete
是一對。正確的代碼是下面這個樣子的:
int main(){
NoName a;
NoName *p = new NoName;
delete p;
return 0;
}
運行輸出:
構造函數被調用了!
構造函數被調用了!
析構函數被調用了!
析構函數被調用了!
構造函數 和 析構函數 各有各的用途,在構造函數中,我們來獲取資源;在析構函數中,我們來釋放資源。釋放了之後,這些資源就會被回收,可以被重新利用。
比如說,我們在構造函數裏打開文件,在析構函數裏關閉打開的文件。這是一個比較好的做法。
在構造函數裏,我們去連接數據庫的連接,在析構函數裏關閉數據庫的連接。
在構造函數裏動態的分配內存,那麼在析構函數裏把動態分配的內存回收。
構造函數 和 析構函數 之間的操作是向對應的。
如果我們不寫析構函數,C++ 會幫我們寫一個析構函數。C++幫我們寫的這個析構函數只能做一些很簡單的工作,它不會幫助我們去打開文件、連接數據庫、分配內存這些操作,相應的回收,它也不會給我們寫。所以需要我們自己手動的寫。(如果要做這些操作,我們必須自己寫。)
如果我們自己寫了析構函數,記住三個原則:
如果你寫了析構函數,就必須同時寫賦值構造函數 和 賦值操作符。你不可能只寫一個。
在賦值的時候,不是講指針賦值過來,而是將指針對應指向的字符串賦值過來,這是最關鍵的一步。
因爲我們寫了析構函數,就必須要將賦值構造函數寫上:
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "構造函數被調用了!" << endl;
}
NoName(const NoName & other);
~NoName();
NoName::NoName(const NoName & other){
pstring = new std::string;
*pstring = *(other.pstring);
i = other.i;
d = other.d;
}
除了要寫 賦值構造函數,還要寫賦值操作符。
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "構造函數被調用了!" << endl;
}
NoName(const NoName & other);
~NoName();
NoName& operator =(const NoName &rhs);12345678910
NoName& NoName::operator=(const NoName &rhs){
pstring = new std::string;
*pstring = *(other.pstring);
i = other.i;
d = other.d;
return *this;
}
只要你寫了析構函數,就必須要寫 賦值構造函數 和 賦值運算符,這就是著名的 三法則 (rule of three)
總結:
在設計一個類的時候,如果我們一個構造函數都沒有寫,那麼 C++ 會幫我們寫一個構造函數。只要我們寫了一個構造函數,那麼 C++ 就不會再幫我們寫構造函數了。
構造函數可以重載,可以寫很多個,析構函數不能重載,只能寫一個。如果我們沒有寫析構函數,C++會自動幫我們寫一個析構函數。那麼在工作的時候,我們寫的析構函數會被調用,調用完成之後,C++會執行它自動生成的析構函數。
如果我們寫的類是一個沒有那麼複雜的類,我們可以不需要寫析構函數。如果一個類只要有這些情況:打開文件、動態分配內存、連接數據庫。簡單的說:就是隻要構造函數裏面有了 new
這個關鍵詞,我們就需要自己手動編寫析構函數。
那麼如果我們寫了析構函數,就必須要注意三法則:同時編寫:析構函數、賦值構造函數、賦值運算符。
完整的代碼:
#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "構造函數被調用了!" << endl;
}
NoName(const NoName & other);
~NoName();
NoName& operator =(const NoName &rhs);
private:
std::string * pstring;
int i;
double d;
};
NoName::~NoName(){
cout << "析構函數被調用了!" << endl;
}
NoName::NoName(const NoName & other){
pstring = new std::string;
*pstring = *(other.pstring);
i = other.i;
d = other.d;
}
NoName& NoName::operator=(const NoName &rhs){
pstring = new std::string;
*pstring = *(rhs.pstring);
i = rhs.i;
d = rhs.d;
return *this;
}
int main(){
NoName a;
NoName *p = new NoName;
delete p;
return 0;
}