深拷貝與淺拷貝
簡單的來說,【淺拷貝】是增加了一個指針,指向原來已經存在的內存。而【深拷貝】是增加了一個指針,並新開闢了一塊空間
讓指針指向這塊新開闢的空間。
【淺拷貝】在多個對象指向一塊空間的時候,釋放一個空間會導致其他對象所使用的空間也被釋放了,再次釋放便會出現錯誤
淺拷貝
爲了形象化說明什麼是深拷貝和淺拷貝,我們就先寫一個String類
類裏面包含【構造函數】,【拷貝構造函數】,【賦值運算符重載】,以及【析構函數】和【輸出操作符“<<”的重載】
- class String
- {
- public:
- String(const char *pStr = "")
- {
- if(NULL == pStr)
- {
- pstr = new char[1];
- *pstr = '\0';
- }
- else
- {
- pstr = new char[strlen(pStr)+1];//加1,某位是'\0'
- strcpy(pstr,pStr);//用拷貝字符串的函數
- }
- }
- String(const String &s)
- :pstr(s.pstr)//淺拷貝的問題,指向同一塊空間,可能造成釋放的錯誤 ,這是淺拷貝的缺點
- {}
- String& operator=(const String&s)
- {
- if(this != &s)
- {
- delete[] pstr;//將原來所指向的空間釋放
- pstr = s.pstr;//讓pstr重新指向s的pstr所指向的空間(也會導致錯誤)
- }
- return *this;
- }
- ~String()
- {
- if(NULL != pstr)
- {
- delete[] pstr;//釋放指針所指向的內容
- pstr = NULL;//將指針置爲空
- }
- }
- friend ostream&operator<<(ostream & _cout,const String &s)
- {
- _cout<<s.pstr;
- return _cout;
- }
- private:
- char *pstr;
- };
經過測試之後,在某種情況下是可以正常運行的,在特定情況下是不可以正常的運行的
舉一個不能正常運行的例子
- int main()
- {
- String s1("sss");
- String s2(s1);
- String s3(NULL);
- s3 = s1;
- cout<<s1<<endl;
- cout<<s2<<endl;
- cout<<s3<<endl;
- return 0;
- }
s2調用【拷貝構造函數】來利用s1進行初始化,s3則用【賦值運算符】來進行初始化
黑框框裏輸出了三個“sss”
然而!
這是爲什麼呢?
通過監視,我們發現他們指向的都是同一塊空間,因爲地址都是【0x01044570】
在讓我們看看【析構函數】
- ~String()
- {
- if (pstr != NULL)
- {
- delete[] pstr;
- pstr = NULL;
- }
- }
然而當釋放s2的時候,由於【s3已經釋放過了】,所以s2所指向的這段空間已經不屬於s1或者s2了
此時我們調用delete釋放的時候,必然會崩潰(畢竟人家本來就不屬於你呀)
深拷貝
深拷貝以及深淺拷貝的對比
深拷貝和淺拷貝的不同之處,僅僅在於修改了下【拷貝構造函數】,以及【賦值運算符的重載】
- String(const String &s)
- :pstr(new char[strlen(s.pstr)+1])
- {
- strcpy(pstr,s.pstr);
- }
- String& operator=(const String &s)
- {
- if(this != &s)
- {
- char* tmp = new char[strlen(s.pstr)+1];//動態開闢一個臨時變量,然後將pstr指向這一個新的臨時變量裏
- delete[] pstr;//將原來的空間進行釋放
- strcpy(tmp,s.pstr);//將s.pstr裏的內容複製到臨時變量中
- pstr = tmp;//pstr指向臨時變量的這段空間
- }
- return *this;
- }
對比一下淺拷貝的【拷貝構造函數】和【賦值運算符重載】
- String(const String &s)
- :pstr(s.pstr)//淺拷貝的問題,指向同一塊空間,可能造成釋放的錯誤 ,這是淺拷貝的缺點
- {}
- String& operator=(const String&s)
- {
- if(this != &s)
- {
- delete[] pstr;//將原來所指向的空間釋放
- pstr = s.pstr;//讓pstr重新指向s的pstr所指向的空間(也會導致錯誤)
- }
- return *this;
- }
深拷貝完整版
- class String
- {
- public:
- String(const char* pStr = "")
- {
- cout<<"String()"<<endl;
- if(NULL == pStr)
- {
- pstr = new char[1];
- *pstr = '\0';
- }
- else
- {
- pstr = new char[strlen(pStr)+1];
- strcpy(pstr,pStr);
- }
- }
- String(const String &s)
- :pstr(new char[strlen(s.pstr)+1])
- {
- strcpy(pstr,s.pstr);
- }
- String& operator=(const String &s)
- {
- if(this != &s)
- {
- char* tmp = new char[strlen(s.pstr)+1];//pstr;
- delete[] pstr;
- strcpy(tmp,s.pstr);
- pstr = tmp;
- }
- return *this;
- }
- ~String()
- {
- if(NULL != pstr)
- {
- delete[] pstr;
- pstr = NULL;
- }
- }
- private:
- char *pstr;
- };
除此之外,我們可以簡化一下深拷貝的【拷貝構造函數】,【賦值運算符重載】
- <span style="color:#000000;">String(const String& s)
- :_ptr(NULL)
- {
- String temp(s._ptr);
- std::swap(_ptr, temp._ptr);
- }
- </span>
在【拷貝構造函數】裏用s定義臨時變量,臨時變量會自動調用構造函數開闢空間
然後用swap這個函數交換兩個變量之間的內容
原來的內容在temp,並且出了【拷貝構造函數】就銷燬了,避免了內存泄漏
- String& operator=(const String& s)
- {
- if (this != &s)
- {
- String temp(s);
- swap(_ptr, temp._ptr);
- }
- return *this;
- }
- String& operator=(const String& s)
- {
- if(this != &s)
- {
- String temp(s._ptr);
- swap(_ptr,temp._ptr);
- }
- return *this;
- }
- String& operator=(String temp)
- {
- swap(_ptr,temp._ptr);
- return *this;
- }
總結
【淺拷貝】只是增加了一個指針,指向已存在對象的內存。
【深拷貝】是增加了一個指針,並新開闢了一塊空間,讓指針指向這塊新開闢的空間。
【淺拷貝】在多個對象指向一塊空間的時候,釋放一個空間會導致其他對象所使用的空間也被釋放了,再次釋放便會出現錯誤