淺拷貝:又稱值拷貝,將源對象的值拷貝到目標對象中去,本質上來說源對象和目標對象共用一份實體,只是所引用的變量名不同,地址其實還是相同的。舉個簡單的例子,你的小名叫西西,大名叫冬冬,當別人叫你西西或者冬冬的時候你都會答應,這兩個名字雖然不相同,但是都指的是你。
假設有一個String類,String s1;String s2(s1);在進行拷貝構造的時候將對象s1里的值全部拷貝到對象s2里。
我們現在來簡單的實現一下這個類
1 #include <iostream> 2 #include<cstring> 3 4 using namespace std; 5 6 class STRING 7 { 8 public: 9 STRING( const char* s = "" ) :_str( new char[strlen(s)+1] ) 10 11 { 12 strcpy_s( _str, strlen(s)+1, s ); 13 } 14 STRING( const STRING& s ) 15 { 16 _str = s._str; 17 } 18 STRING& operator=(const STRING& s) 19 { 20 if (this != &s) 21 { 22 this->_str = s._str; 23 } 24 return *this; 25 } 26 27 ~STRING() 28 { 29 cout << "~STRING" << endl; 30 if (_str) 31 { 32 delete[] _str; 33 _str = NULL; 34 } 35 } 36 37 void show() 38 { 39 cout << _str << endl; 40 } 41 private: 42 char* _str; 43 }; 44 45 int main() 46 { 47 STRING s1("hello linux"); 48 STRING s2(s1); 49 s2.show(); 50 51 return 0; 52 }
其實這個程序是存在問題的,什么問題呢?我們想一下,創建s2的時候程序必然會去調用拷貝構造函數,這時候拷貝構造僅僅只是完成了值拷貝,導致兩個指針指向了同一塊內存區域。隨着程序的運行結束,又去調用析構函數,先是s2去調用析構函數,釋放了它指向的內存區域,接着s1又去調用析構函數,這時候析構函數企圖釋放一塊已經被釋放的內存區域,程序將會崩潰。s1和s2的關系就是這樣的:
進行調試程序發現s1和s2確實指向了同一塊區域:
所以程序會崩潰是應該的,那么這個問題應該怎么去解決呢?這就引出了深拷貝。
深拷貝,拷貝的時候先開辟出和源對象大小一樣的空間,然后將源對象里的內容拷貝到目標對象中去,這樣兩個指針就指向了不同的內存位置。並且里面的內容是一樣的,這樣不但達到了我們想要的目的,還不會出現問題,兩個指針先后去調用析構函數,分別釋放自己所指向的位置。即為每次增加一個指針,便申請一塊新的內存,並讓這個指針指向新的內存,深拷貝情況下,不會出現重復釋放同一塊內存的錯誤。
深拷貝實際上是這樣的:
深拷貝的拷貝構造函數和賦值運算符的重載傳統實現:
1 STRING( const STRING& s ) 2 { 3 //_str = s._str; 4 _str = new char[strlen(s._str) + 1]; 5 strcpy_s( _str, strlen(s._str) + 1, s._str ); 6 } 7 STRING& operator=(const STRING& s) 8 { 9 if (this != &s) 10 { 11 //this->_str = s._str; 12 delete[] _str; 13 this->_str = new char[strlen(s._str) + 1]; 14 strcpy_s(this->_str, strlen(s._str) + 1, s._str); 15 } 16 return *this; 17 }
這里的拷貝構造函數我們很容易理解,先開辟出和源對象一樣大的內存區域,然后將需要拷貝的數據復制到目標拷貝對象,
那么這里的賦值運算符的重載是怎么樣做的呢?
這種方法解決了我們的指針懸掛問題,通過不斷的開空間讓不同的指針指向不同的內存,以防止同一塊內存被釋放兩次的問題,還有一種深拷貝的現代寫法:
1 STRING( const STRING& s ):_str(NULL) 2 { 3 STRING tmp(s._str);// 調用了構造函數,完成了空間的開辟以及值的拷貝 4 swap(this->_str, tmp._str); //交換tmp和目標拷貝對象所指向的內容 5 } 6 7 STRING& operator=(const STRING& s) 8 { 9 if ( this != &s )//不讓自己給自己賦值 10 { 11 STRING tmp(s._str);//調用構造函數完成空間的開辟以及賦值工作 12 swap(this->_str, tmp._str);//交換tmp和目標拷貝對象所指向的內容 13 } 14 return *this; 15 }
先來分析一下拷貝構造是怎么實現的:
拷貝構造調用完成之后,會接着去調用析構函數來銷毀局部對象tmp,按照這種思路,不難可以想到s2的值一定和拷貝構造里的tmp的值一樣,指向同一塊內存區域,通過調試可以看出來:
在拷貝構造函數里的tmp:
調用完拷貝構造后的s2:(此時tmp被析構)
可以看到s2的地址值和拷貝構造里的tmp的地址值是一樣
關於賦值運算符的重載還可以這樣來寫:
STRING& operator=(STRING s)
{
swap(_str, s._str);
return *this;
}
1 #include <iostream> 2 #include<cstring> 3 4 using namespace std; 5 6 class STRING 7 { 8 public: 9 STRING( const char* s = "" ) :_str( new char[strlen(s)+1] ) 10 11 { 12 strcpy_s( _str, strlen(s)+1, s ); 13 } 14 //STRING( const STRING& s ) 15 //{ 16 // //_str = s._str; //淺拷貝的寫法 17 // cout << "拷貝構造函數" << endl; 18 // _str = new char[strlen(s._str) + 1]; 19 // strcpy_s( _str, strlen(s._str) + 1, s._str ); 20 //} 21 //STRING& operator=(const STRING& s) 22 //{ 23 // cout << "運算符重載" << endl; 24 // if (this != &s) 25 // { 26 // //this->_str = s._str; //淺拷貝的寫法 27 // delete[] _str; 28 // this->_str = new char[strlen(s._str) + 1]; 29 // strcpy_s(this->_str, strlen(s._str) + 1, s._str); 30 // } 31 // return *this; 32 //} 33 34 STRING( const STRING& s ):_str(NULL) 35 { 36 STRING tmp(s._str);// 調用了構造函數,完成了空間的開辟以及值的拷貝 37 swap(this->_str, tmp._str); //交換tmp和目標拷貝對象所指向的內容 38 } 39 40 STRING& operator=(const STRING& s) 41 { 42 if ( this != &s )//不讓自己給自己賦值 43 { 44 STRING tmp(s._str);//調用構造函數完成空間的開辟以及賦值工作 45 swap(this->_str, tmp._str);//交換tmp和目標拷貝對象所指向的內容 46 } 47 return *this; 48 } 49 50 ~STRING() 51 { 52 cout << "~STRING" << endl; 53 if (_str) 54 { 55 delete[] _str; 56 _str = NULL; 57 } 58 } 59 60 void show() 61 { 62 cout << _str << endl; 63 } 64 private: 65 char* _str; 66 }; 67 68 int main() 69 { 70 //STRING s1("hello linux"); 71 //STRING s2(s1); 72 //STRING s2 = s1; 73 //s2.show(); 74 const char* str = "hello linux!"; 75 STRING s1(str); 76 STRING s2; 77 s2 = s1; 78 s1.show(); 79 s2.show(); 80 81 return 0; 82 }
參考與
淺析C++中的深淺拷貝 - qq_39344902的博客 - CSDN博客
https://blog.csdn.net/qq_39344902/article/details/79798297