C++:析構函數
一、什麽是析構函數
析構函數是類中一種特殊的成員函數,但其功能和構造函數是相反的,當對象結束其生命周期時,系統會自動調用該對象的析構函數進行清理工作(如釋放內存中分配給該對象的空間,關閉打開的文件等)。另外析構函數沒有返回值,不需要參數,也不能被重載且一個類中有且只能有一個析構函數。但和構造函數相似,析構函數的函數名和類名相同,只不過需要在函數名向加上一個~。
語法:~ 類名(){/*...析構函數體...*/}
特別註意:
? 如果用戶沒有顯式地在類中定義析構函數,編譯器會在類中生成一個默認的析構函數。且任何對象在被系統銷毀時,都會調用該對象的析構函數。
1 #include<iostream> 2#include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"調用了默認構造函數"<<endl; 8 } 9 Student(string name,int age):Name(name),Age(age){} 10 Student(const Student& stu);//拷貝構造函數 11 Student& operator=(const Student& stu);//賦值函數 12 ~Student(){ //析構函數 13 cout<<"調用了析構函數"<<endl; 14 } 15 private: 16 string Name; 17 int Age; 18 }; 19 int main(){ 20 Student stu1; 21 return 0; 22 }
? main函數中,析構函數的調用發生在語句“return 0;”之後
二、對象的析構順序
先來看一個例子
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"調用了默認構造函數"<<endl; 8 } 9 Student(string name,int age,int id):Name(name),Age(age),ID(id){ 10 cout<<"創建對象ID號為"<<this->ID<<"的對象"<<endl; 11 } 12 Student(const Student& stu);//拷貝構造函數 13 Student& operator=(const Student& stu);//賦值函數 14 ~Student(){ //析構函數 15 cout<<"析構對象ID號為"<<this->ID<<"的對象"<<endl; 16 } 17 private: 18 string Name; 19 int Age; 20 int ID;//對象的ID號 21 }; 22 int main(){ 23 Student stu1("Tomwenxing",23,1); 24 Student stu2("Ellen",22,2); 25 Student stu3("Jack",21,3); 26 Student stu4("Dick",23,4); 27 cout<<"---------------分界線---------------------"<<endl; 28 return 0; 29 }
由上例可知,對象創建完畢後會被存放在內存中的一個棧中,因此根據棧的“先進後出”的原則,最後被創建的對象由於最晚添加到棧中,故會被最先析構;而最早創建的對象由於位於棧底,故最後才會被析構。
三、為什麽編寫析構函數
在C++中,如果用戶在自定義的類中沒有編寫析構函數,那麽編譯器會在類中自動生成一個默認的析構函數,但通常情況下編譯器生成的默認析構函數的函數體為空,即該析構函數什麽工作也不做,例如上例中的Student類,如果不手動定義該類的析構函數,那麽系統默認生成的析構函數如下:
1 Student::~Student(){}//該析構函數什麽工作也不做
但有時候我們需要析構函數在銷毀對象時完成一些清理工作。例如C++要求如果用new在內存中動態開辟了空間,則必須由相應的delete對該內存空間進行釋放,因此如果我們自定義的類中含有指針成員變量,並在該類的構造函數中用new為該指針在內存中動態的分配空間,那麽為了避免內存泄漏,就必須在該類的析構函數中使用delete來釋放在內存中動態開辟的空間:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Example{ 5 public: 6 Example()=default; 7 Example(string message,int v):ptr(new string) ,ID(v){ //用new為指針ptr分配內存空間 8 *ptr=message; //將信息拷貝到動態分配的內存空間中 9 cout<<"創建對象ID為"<<this->ID<<"的對象"<<endl; 10 } 11 ~Example(){ 12 cout<<"析構對象ID為"<<this->ID<<"的對象"<<endl; 13 delete ptr;//釋放ptr所指的內存空間 14 ptr=NULL; //將指針ptr賦值為空 15 cout<<"delete完畢!"<<endl; 16 } 17 private: 18 string *ptr;//指針 19 int ID; 20 }; 21 22 int main(){ 23 Example example1("Tomwenxing",1); 24 Example example2("JackMa",2); 25 return 0; 26 }
再比如我們可以在構造函數中打開文件,而在析構函數中關閉文件;或在構造函數中打開和數據庫的連接,而在析構函數中關閉和數據庫的連接。等等......
因此簡單來說就是在構造函數中獲取系統資源,而在析構函數中釋放這些系統資源以便這些系統資源被重新利用。
四、一個重要的原則——三法則(rule of three)
如果用戶顯示定義了類中析構函數、拷貝構造函數或賦值函數中的任何一個,那麽另外兩個函數通常也必須顯式定義。
Question:什麽時候需要顯示定義?
Answer:當自定義類中有指針成員變量或需要利用某種系統資源時(如文件資源、數據庫資源等)時,往往需要顯示定義析構函數、拷貝構造函數和賦值函數。
C++:析構函數