C++複習(五):淺拷貝和深拷貝

深拷貝與淺拷貝

簡單的來說,【淺拷貝】是增加了一個指針,指向原來已經存在的內存。而【深拷貝】是增加了一個指針,並新開闢了一塊空間

讓指針指向這塊新開闢的空間。

【淺拷貝】在多個對象指向一塊空間的時候,釋放一個空間會導致其他對象所使用的空間也被釋放了,再次釋放便會出現錯誤

淺拷貝

爲了形象化說明什麼是深拷貝和淺拷貝,我們就先寫一個String類

類裏面包含【構造函數】【拷貝構造函數】【賦值運算符重載】,以及【析構函數】【輸出操作符“<<”的重載】

  1. class String  
  2. {  
  3. public:  
  4.     String(const char *pStr = "")  
  5.     {  
  6.         if(NULL == pStr)  
  7.         {  
  8.             pstr = new char[1];  
  9.             *pstr = '\0';  
  10.         }  
  11.         else  
  12.         {  
  13.             pstr = new char[strlen(pStr)+1];//加1,某位是'\0'  
  14.             strcpy(pstr,pStr);//用拷貝字符串的函數   
  15.         }  
  16.     }  
  17.       
  18.     String(const String &s)  
  19.         :pstr(s.pstr)//淺拷貝的問題,指向同一塊空間,可能造成釋放的錯誤 ,這是淺拷貝的缺點   
  20.     {}  
  21.       
  22.     String& operator=(const String&s)  
  23.     {  
  24.         if(this != &s)  
  25.         {  
  26.             delete[] pstr;//將原來所指向的空間釋放   
  27.             pstr = s.pstr;//讓pstr重新指向s的pstr所指向的空間(也會導致錯誤)   
  28.         }   
  29.         return *this;  
  30.     }  
  31.       
  32.     ~String()  
  33.     {  
  34.         if(NULL != pstr)  
  35.         {  
  36.             delete[] pstr;//釋放指針所指向的內容   
  37.             pstr = NULL;//將指針置爲空      
  38.         }   
  39.     }   
  40.     friend ostream&operator<<(ostream & _cout,const String &s)  
  41.     {  
  42.         _cout<<s.pstr;  
  43.         return _cout;  
  44.     }  
  45. private:  
  46.     char *pstr;       
  47. };  

經過測試之後,在某種情況下是可以正常運行的,在特定情況下是不可以正常的運行的

舉一個不能正常運行的例子

  1. int main()  
  2. {     
  3.     String s1("sss");  
  4.     String s2(s1);  
  5.     String s3(NULL);  
  6.     s3 = s1;  
  7.     cout<<s1<<endl;  
  8.     cout<<s2<<endl;  
  9.     cout<<s3<<endl;  
  10.     return 0;  
  11. }  
在該例子中,我們有三個String類的對象,s1調用【構造函數】存入字符"sss"

s2調用【拷貝構造函數】來利用s1進行初始化,s3則用【賦值運算符】來進行初始化

黑框框裏輸出了三個“sss”

然而!


這是爲什麼呢?

通過監視,我們發現他們指向的都是同一塊空間,因爲地址都是【0x01044570】

在讓我們看看【析構函數】

  1. ~String()  
  2. {  
  3.     if (pstr != NULL)  
  4.     {  
  5.         delete[] pstr;  
  6.         pstr = NULL;  
  7.     }  
  8. }  
當我們釋放s3的時候,可以正常釋放

然而當釋放s2的時候,由於【s3已經釋放過了】,所以s2所指向的這段空間已經不屬於s1或者s2了

此時我們調用delete釋放的時候,必然會崩潰(畢竟人家本來就不屬於你呀)


深拷貝

深拷貝以及深淺拷貝的對比

深拷貝和淺拷貝的不同之處,僅僅在於修改了下【拷貝構造函數】,以及【賦值運算符的重載】

  1. String(const String &s)  
  2.     :pstr(new char[strlen(s.pstr)+1])  
  3. {  
  4.     strcpy(pstr,s.pstr);   
  5. }  
  6.   
  7. String& operator=(const String &s)  
  8. {  
  9.     if(this != &s)  
  10.     {  
  11.         char* tmp = new char[strlen(s.pstr)+1];//動態開闢一個臨時變量,然後將pstr指向這一個新的臨時變量裏  
  12.         delete[] pstr;//將原來的空間進行釋放  
  13.         strcpy(tmp,s.pstr);//將s.pstr裏的內容複製到臨時變量中  
  14.         pstr = tmp;//pstr指向臨時變量的這段空間  
  15.     }  
  16.     return *this;  
  17. }  

對比一下淺拷貝的【拷貝構造函數】【賦值運算符重載】

  1. String(const String &s)  
  2.     :pstr(s.pstr)//淺拷貝的問題,指向同一塊空間,可能造成釋放的錯誤 ,這是淺拷貝的缺點   
  3. {}  
  4.   
  5. String& operator=(const String&s)  
  6. {  
  7.     if(this != &s)  
  8.     {  
  9.         delete[] pstr;//將原來所指向的空間釋放   
  10.         pstr = s.pstr;//讓pstr重新指向s的pstr所指向的空間(也會導致錯誤)   
  11.     }   
  12.     return *this;  
  13. }  


深拷貝完整版

  1. class String  
  2. {  
  3. public:  
  4.     String(const char* pStr = "")  
  5.     {     
  6.         cout<<"String()"<<endl;  
  7.         if(NULL == pStr)  
  8.         {  
  9.             pstr = new char[1];  
  10.             *pstr = '\0';  
  11.         }  
  12.         else  
  13.         {  
  14.             pstr = new char[strlen(pStr)+1];  
  15.             strcpy(pstr,pStr);   
  16.         }  
  17.     }  
  18.     String(const String &s)  
  19.         :pstr(new char[strlen(s.pstr)+1])  
  20.     {  
  21.         strcpy(pstr,s.pstr);   
  22.     }  
  23.       
  24.     String& operator=(const String &s)  
  25.     {  
  26.         if(this != &s)  
  27.         {  
  28.             char* tmp = new char[strlen(s.pstr)+1];//pstr;  
  29.             delete[] pstr;  
  30.             strcpy(tmp,s.pstr);  
  31.             pstr = tmp;  
  32.         }  
  33.         return *this;  
  34.     }  
  35.       
  36.     ~String()  
  37.     {  
  38.         if(NULL != pstr)  
  39.         {  
  40.             delete[] pstr;  
  41.             pstr = NULL;      
  42.         }  
  43.     }     
  44. private:  
  45.     char *pstr;  
  46. };  

除此之外,我們可以簡化一下深拷貝的【拷貝構造函數】【賦值運算符重載】

  1. <span style="color:#000000;">String(const String& s)  
  2. :_ptr(NULL)  
  3. {  
  4.     String temp(s._ptr);  
  5.     std::swap(_ptr, temp._ptr);  
  6. }  
  7. </span>  

【拷貝構造函數】裏用s定義臨時變量,臨時變量會自動調用構造函數開闢空間

然後用swap這個函數交換兩個變量之間的內容

原來的內容在temp,並且出了【拷貝構造函數】就銷燬了,避免了內存泄漏

  1. String& operator=(const String& s)  
  2. {  
  3.     if (this != &s)  
  4.     {  
  5.         String temp(s);  
  6.         swap(_ptr, temp._ptr);  
  7.     }  
  8.     return *this;  
  9. }  
  1. String& operator=(const String& s)  
  2. {  
  3.     if(this != &s)  
  4.     {  
  5.         String temp(s._ptr);  
  6.         swap(_ptr,temp._ptr);  
  7.     }  
  8.     return *this;  
  9. }  
  1. String& operator=(String temp)  
  2. {  
  3.     swap(_ptr,temp._ptr);  
  4.     return *this;  
  5. }  
【賦值運算符重載】裏,也可以用到類似的方法。在第三個方法裏,直接傳入一個臨時變量,連if判斷都可以省去了

總結

【淺拷貝】只是增加了一個指針,指向已存在對象的內存。

【深拷貝】是增加了一個指針,並新開闢了一塊空間,讓指針指向這塊新開闢的空間。

【淺拷貝】在多個對象指向一塊空間的時候,釋放一個空間會導致其他對象所使用的空間也被釋放了,再次釋放便會出現錯誤

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章