一、淺拷貝
本質:簡單地復制對象的成員值。如果成員里有指針,新對象和原對象的指針會指向同一塊內存。
比如你有對象?A
,里面指針?p
?指向堆內存?0x123
;用?A
?拷貝出對象?B
,B
?的指針?p
?也指向?0x123
。問題:
- 若其中一個對象修改指針指向的內容,另一個對象也會受影響(因為共享內存)。
- 當對象銷毀時(調用析構函數),兩個對象的指針會重復釋放同一塊內存,程序會崩潰(C++ 不允許重復釋放同一塊堆內存 )。
類比:你和朋友共用一把家里的鑰匙(指針),你把家里東西改了(修改內存內容),朋友看到的也會變;但如果你們先后 “銷毀鑰匙權限”(調用析構釋放內存),第二次釋放就會出問題。
二、深拷貝
本質:給新對象的指針重新開一塊獨立的堆內存,再把原對象指針指向的內容完整復制到新內存里。
還是用上面的例子:對象?A
?指針?p
?指向?0x123
;拷貝出對象?B
?時,B
?會新申請一塊內存?0x456
,再把?0x123
?里的內容復制到?0x456
,B
?的指針?p
?指向?0x456
。優點:
- 兩個對象的指針各自指向獨立內存,修改內容互不影響。
- 析構時,各自釋放自己的內存,不會重復釋放,程序更安全。
類比:你和朋友各自配一把鑰匙(新內存),各自鑰匙開各自家門(獨立內存),改自己家東西不影響對方,銷毀鑰匙(析構)也不會沖突。
三、如何實現深拷貝?(以代碼為例)
假設我們有一個簡單的?STRING
?類(簡化版,類似你提供的代碼):
cpp
運行
class STRING {
private:char* _str; // 指向堆內存的指針
public:// 構造函數:初始化字符串STRING(const char* str) {_str = new char[strlen(str) + 1]; // 開堆內存存字符串strcpy(_str, str);}// 析構函數:釋放堆內存~STRING() {delete[] _str;}// ... 其他成員函數
};
1. 深拷貝的拷貝構造函數
cpp
運行
STRING(const STRING& s) {// 1. 給新對象的指針開獨立堆內存(大小和原字符串一樣,+1 存 '\0')_str = new char[strlen(s._str) + 1]; // 2. 把原對象字符串內容,復制到新內存里strcpy(_str, s._str);
}
作用:創建新對象時,不共用原對象內存,而是 “另開新內存 + 復制內容”,避免淺拷貝的問題。
2. 深拷貝的賦值運算符重載
cpp
運行
STRING& operator=(const STRING& s) {// 防御性檢查:避免自己賦值給自己(比如 a = a; 這種情況,釋放內存會出問題)if (this != &s) { // 1. 先釋放當前對象舊的堆內存(防止內存泄漏)delete[] _str; // 2. 開新內存,復制內容(和拷貝構造邏輯一樣)_str = new char[strlen(s._str) + 1]; strcpy(_str, s._str); }return *this; // 返回當前對象,支持鏈式賦值(比如 a = b = c; )
}
作用:處理 “對象賦值” 場景(比如?a = b;
?)。需要先釋放自己舊的內存,再深拷貝新內容,否則會內存泄漏(舊內存沒釋放,又開新內存,原內存就丟了,無法釋放 )。
四、總結
- 淺拷貝:簡單復制指針值,共享內存,容易出 “重復釋放” 或 “內容互相影響” 的問題。
- 深拷貝:給新對象指針重新開內存、復制內容,讓對象互相獨立,解決淺拷貝的隱患。
- 實現關鍵:在拷貝構造函數和賦值運算符重載里,手動 “開新內存 + 復制內容”,別依賴編譯器默認的淺拷貝邏輯。
理解后,寫涉及指針成員(動態內存)的類時,記得補全深拷貝的這兩個函數,否則程序大概率會崩潰或內存泄漏~