1. 程式人生 > >C++:析構函數

C++:析構函數

原則 efault main函數 內存空間 log 文件 student 功能 namespace

一、什麽是析構函數

析構函數是類中一種特殊的成員函數,但其功能和構造函數是相反的,當對象結束其生命周期時,系統會自動調用該對象的析構函數進行清理工作(如釋放內存中分配給該對象的空間,關閉打開的文件等)。另外析構函數沒有返回值,不需要參數,也不能被重載且一個類中有且只能有一個析構函數。但和構造函數相似,析構函數的函數名和類名相同,只不過需要在函數名向加上一個~

語法:~ 類名(){/*...析構函數體...*/}

特別註意:

? 如果用戶沒有顯式地在類中定義析構函數,編譯器會在類中生成一個默認的析構函數。且任何對象在被系統銷毀時,都會調用該對象的析構函數。

 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++:析構函數