C++ 中的深拷貝與淺拷貝

  淺拷貝:又稱值拷貝,將源對象的值拷貝到目標對象中去,本質上來講源對象和目標對象共用一份實體,只是所引用的變量名不一樣,地址其實仍是相同的。舉個簡單的例子,你的小名叫西西,大名叫鼕鼕,當別人叫你西西或者鼕鼕的時候你都會答應,這兩個名字雖然不相同,可是都指的是你。linux

  假設有一個String類,String s1;String s2(s1);在進行拷貝構造的時候將對象s1裏的值所有拷貝到對象s2裏。ios

  咱們如今來簡單的實現一下這個類函數

 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的關係就是這樣的:this

進行調試程序發現s1和s2確實指向了同一塊區域:spa

因此程序會崩潰是應該的,那麼這個問題應該怎麼去解決呢?這就引出了深拷貝。.net

深拷貝,拷貝的時候先開闢出和源對象大小同樣的空間,而後將源對象裏的內容拷貝到目標對象中去,這樣兩個指針就指向了不一樣的內存位置。而且裏面的內容是同樣的,這樣不但達到了咱們想要的目的,還不會出現問題,兩個指針前後去調用析構函數,分別釋放本身所指向的位置。即爲每次增長一個指針,便申請一塊新的內存,並讓這個指針指向新的內存,深拷貝狀況下,不會出現重複釋放同一塊內存的錯誤。指針

深拷貝其實是這樣的:調試

深拷貝的拷貝構造函數和賦值運算符的重載傳統實現:code

 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

相關文章
相關標籤/搜索