C++:析构函数
創建對象時系統會自動調用構造函數進行初始化工作,同樣,銷毀對象時系統也會自動調用一個函數來進行清理工作,例如釋放分配的內存、關閉打開的文件等,這個函數就是析構函數。
析構函數(destructor) 與構造函數相反,當對象結束其生命周期,如對象所在的函數已調用完畢時,系統自動執行析構函數。析構函數往往用來做“清理善后” 的工作(例如在建立對象時用new開辟了一片內存空間,delete會自動調用析構函數后釋放內存)。
作用:“清理善后”工作 ? ? ? ?? 命名方式:與類名相同,在前面加位取反符~
與構造函數相反,當對象結束其生命周期,如對象所在的函數已調用完畢時,系統會自動執行析構函數。以C++語言為例:析構函數名也應與類名相同,只是在函數名前面加一個位取反符~,例如~stud( ),以區別于構造函數。它不能帶任何參數,也沒有返回值(包括void類型),(析構函數(Destructor)也是一種特殊的成員函數,沒有返回值,不需要程序員顯式調用(程序員也沒法顯式調用),而是在銷毀對象時自動執行。構造函數的名字和類名相同,而析構函數的名字是在類名前面加一個~符號)。只能有一個析構函數,不能重載。如果用戶沒有編寫析構函數,編譯系統會自動生成一個缺省的析構函數(即使自定義了析構函數,編譯器也總是會為我們合成一個析構函數,并且如果自定義了析構函數,編譯器在執行時會先調用自定義的析構函數再調用合成的析構函數),它也不進行任何操作。所以許多簡單的類中沒有用顯式的析構函數。
C++中的析構函數格式:
class <類名>
{public:~<類名>();
};
<類名>::~<類名>()
{//函數體
}
以下方式定義也是可以的:
class T
{public:~T();
};T::~T()
{//函數體
}
PS:注意:析構函數沒有參數,不能被重載,因此一個類只能有一個析構函數。如果用戶沒有定義,編譯器會自動生成一個默認的析構函數。當程序中沒有析構函數時,系統會自動生成以下析構函數:
<類名>::~<類名>(){},即不執行任何操作。
定義了一個 VLA 類來模擬變長數組,它使用一個構造函數為數組分配內存,這些內存在數組被銷毀后不會自動釋放,所以非常有必要再添加一個析構函數,專門用來釋放已經分配的內存。請看下面的完整示例:
#include <iostream>
using namespace std;class VLA{
public:VLA(int len); //構造函數~VLA(); //析構函數
public:void input(); //從控制臺輸入數組元素void show(); //顯示數組元素
private:int *at(int i); //獲取第i個元素的指針
private:const int m_len; //數組長度int *m_arr; //數組指針int *m_p; //指向數組第i個元素的指針
};VLA::VLA(int len): m_len(len){ //使用初始化列表來給 m_len 賦值if(len > 0){ m_arr = new int[len]; /*分配內存*/ }else{ m_arr = NULL; }
}
VLA::~VLA(){delete[] m_arr; //釋放內存
}
void VLA::input(){for(int i=0; m_p=at(i); i++){ cin>>*at(i); }
}
void VLA::show(){for(int i=0; m_p=at(i); i++){if(i == m_len - 1){ cout<<*at(i)<<endl; }else{ cout<<*at(i)<<", "; }}
}
int * VLA::at(int i){if(!m_arr || i<0 || i>=m_len){ return NULL; }else{ return m_arr + i; }
}int main(){//創建一個有n個元素的數組(對象)int n;cout<<"Input array length: ";cin>>n;VLA *parr = new VLA(n);//輸入數組元素cout<<"Input "<<n<<" numbers: ";parr -> input();//輸出數組元素cout<<"Elements: ";parr -> show();//刪除數組(對象)delete parr;return 0;
}
運行結果:
Input array length: 5
Input 5 numbers: 99 23 45 10 100
Elements: 99, 23, 45, 10, 100
~VLA()就是 VLA 類的析構函數,它的唯一作用就是在刪除對象(第 53 行代碼)后釋放已經分配的內存。
函數名是標識符的一種,原則上標識符的命名中不允許出現~符號,在析構函數的名字中出現的~可以認為是一種特殊情況,目的是為了和構造函數的名字加以對比和區分。
注意:at() 函數只在類的內部使用,所以將它聲明為 private 屬性;m_len 變量不允許修改,所以用 const 進行了限制,這樣就只能使用初始化列表來進行賦值。
C++ 中的 new 和 delete 分別用來分配和釋放內存,它們與C語言中 malloc()、free() 最大的一個不同之處在于:用 new 分配內存時會調用構造函數,用 delete 釋放內存時會調用析構函數。構造函數和析構函數對于類來說是不可或缺的,所以在C++中我們非常鼓勵使用 new 和 delete。
--------析構函數的執行時機:
析構函數在對象被銷毀時調用,而對象的銷毀時機與它所在的內存區域有關。
在所有函數之外創建的對象是全局對象,它和全局變量類似,位于內存分區中的全局數據區,程序在結束執行時會調用這些對象的析構函數。
在函數內部創建的對象是局部對象,它和局部變量類似,位于棧區,函數執行結束時會調用這些對象的析構函數。
new 創建的對象位于堆區,通過 delete 刪除時才會調用析構函數;如果沒有 delete,析構函數就不會被執行。
下面的例子演示了析構函數的執行:
#include <iostream>
#include <string>
using namespace std;class Demo{
public:Demo(string s);~Demo();
private:string m_s;
};
Demo::Demo(string s): m_s(s){ }
Demo::~Demo(){ cout<<m_s<<endl; }void func(){//局部對象Demo obj1("1");
}//全局對象
Demo obj2("2");int main(){//局部對象Demo obj3("3");//new創建的對象Demo *pobj4 = new Demo("4");func();cout<<"main"<<endl;return 0;
}
運行結果:
1
main
3
2
?
?
總結