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:當自定義類中有指針成員變量或需要利用某種系統資源時(如文件資源、數據庫資源等)時,往往需要顯示定義析構函數、拷貝構造函數和賦值函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM