記得11年底找工作的時候,面試時曾經遇到有面試官問的對深拷貝與淺拷貝的理解,那時候自己回來查了資料,寫了篇部落格,感覺自己理解了,其實理解的不深刻,最近在除錯
bug的時候,再次遇到深拷貝與淺拷貝,認真分析了,寫寫自己的心得吧。
先說下自己的理解吧,淺拷貝,即在定義乙個類a
,使用類似
a obj; a obj1(obj);
或者a obj1 = obj;
時候,由於沒有自定義拷貝建構函式,
c++編譯器自動會產生乙個預設的拷貝建構函式。這個預設的拷貝建構函式採用的是「位拷貝」(淺拷貝),而非「值拷貝」(深拷貝)的方式,如果類中含有指標變數,預設的拷貝建構函式必定出錯。
用一句簡單的話來說就是淺拷貝,只是對指標的拷貝,拷貝後兩個指標指向同乙個記憶體空間,深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同位址的指標。
淺拷貝會出現什麼問題呢?
假如有乙個成員變數的指標,char *m_data;
其一,淺拷貝只是拷貝了指標,使得兩個指標指向同乙個位址,這樣在物件塊結束,呼叫函式析構的時,會造成同乙份資源析構2
次,即delete
同一塊記憶體
2次,造成程式崩潰。
其二,淺拷貝使得obj.m_data
和obj1.m_data
指向同一塊記憶體,任何一方的變動都會影響到另一方。
其三,在釋放記憶體的時候,會造成obj1.m_data
原有的記憶體沒有被釋放(這句話,剛開始我不太理解,如果沒有走自定義的拷貝建構函式,申請記憶體空間,a obj1(obj);
也不走預設建構函式,走的是預設的拷貝建構函式,何來分配空間直說,更不會造成
obj1.m_data
原有的記憶體沒有被釋放,這裡剛開始我一直有疑問),造成記憶體洩露。
事實是這樣的,當delete obj.m_data, obj.m_data
記憶體被釋放後,由於之前
obj.m_data
和obj1.m_data
指向的是同乙個記憶體空間,
obj1.m_data
所指的空間不能在被利用了,
delete obj1.m_data
也不會成功,一致已經無法操作該空間,所以導致記憶體洩露。
深拷貝採用了在堆記憶體中申請新的空間來儲存資料,這樣每個可以避免指標懸掛。
下面來看看類string
的拷貝建構函式
class string
; string(const string &other)
可以看到在拷貝建構函式中為成員變數申請了新的記憶體空間,這就使得兩個物件的成員變數不指向同乙個記憶體空間,除非你的確需要這樣做,用於實現一些其他的用途。
淺拷貝:也就是在物件複製時,只是對物件中的資料成員進行簡單的賦值,如果物件中存在動態成員,即指標,淺拷貝就會出現問題,下面**:
#include class a
~a() // 析構函式,釋放動態分配的空間
}private:
char *m_data; // 一指標成員
};int main()
執行結果:
*** glibc detected *** ./******: double free or corruption (fasttop): 0x000000000c62a010 ***
分析:由於沒有拷貝建構函式,走編譯器預設的拷貝建構函式,a b(a);
進行物件析構時,會造成釋放同一記憶體空間
2次,導致記憶體洩露。
深拷貝:對於深拷貝,針對成員變數存在指標的情況,不僅僅是簡單的指標賦值,而是重新分配記憶體空間,如下:
#include #include class a
a(const a& r)
~a() // 析構函式,釋放動態分配的空間
}private:
char *m_pdata; // 一指標成員
};int main()
下面是我在具體的應用中使用深拷貝的情況,現在把這個demo
貼出來:
#include #include #include #include using namespace std;
/*儲存記錄資訊的結構體*/
typedef struct _recoder_value_stru
recoder_value_stru;
class recorder
//拷貝建構函式
/* recorder(const recorder &recorder)
*///建構函式
recorder(int iid, int iage)
~recorder()
對比結果:
注釋掉自定義拷貝建構函式,
執行結果:
測試預設建構函式
預設 construct recorder->&m_stru_recvalue: ddbb8de0, m_precvalue: ddbb8de0 m_paddr: 1b8a0010
非參:btest ->&m_stru_recvalue: ddbb8de0 addr: ddbb8de0 m_paddr: 1b8a0010
非參:btest1->&m_stru_recvalue: ddbb8dc0 addr: ddbb8de0 m_paddr: 1b8a0010
測試帶引數的建構函式
construct recorder->&m_stru_recvalue: ddbb8da0 m_precvalue: ddbb8da0 m_paddr: 1b8a0080
帶參:btest2->m_stru_recvalue: ddbb8da0 m_precvalue: ddbb8da0 , m_paddr: 1b8a0080
帶參:btest3->m_stru_recvalue: ddbb8d80 m_precvalue: ddbb8da0 , m_paddr: 1b8a0080
預設拷貝建構函式結果分析:
通過結果可以看出,當成員變數為指標變數的時候,指標成員變數指向的位址都是同乙個位址,無論是申請空間的成員變數m_precvalue
,和僅僅作為指標賦值的成員變數
m_paddr
;結構體的位址是變化的,除了指標淺拷貝與深拷貝沒什麼區別。
開啟自定義拷貝建構函式,執行結果:
測試預設建構函式
預設 construct recorder->&m_stru_recvalue: 58bb9e20, m_precvalue: 58bb9e20 m_paddr: 7a2c010
拷貝 construct recorder->&m_stru_recvalue: 58bb9e00 m_precvalue: 58bb9e00 m_paddr: 7a2c080
非參:btest ->&m_stru_recvalue: 58bb9e20 addr: 58bb9e20 m_paddr: 7a2c010
非參:btest1->&m_stru_recvalue: 58bb9e00 addr: 58bb9e00 m_paddr: 7a2c080
測試帶引數的建構函式
construct recorder->&m_stru_recvalue: 58bb9de0 m_precvalue: 58bb9de0 m_paddr: 7a2c0f0
拷貝 construct recorder->&m_stru_recvalue: 58bb9dc0 m_precvalue: 58bb9dc0 m_paddr: 7a2c160
帶參:btest2->m_stru_recvalue: 58bb9de0 m_precvalue: 58bb9de0 , m_paddr: 7a2c0f0
帶參:btest3->m_stru_recvalue: 58bb9dc0 m_precvalue: 58bb9dc0 , m_paddr: 7a2c160
自定義深拷貝建構函式結果分析:
從結果可以看出,所有成員變數的位址都不相同。
其他:1. 有時候為了防止預設拷貝發生,可以宣告乙個私有的拷貝建構函式(不用寫**),這樣的話,如果試圖呼叫 a b(a); 就呼叫了私有的拷貝建構函式,編譯器會報錯,這也是一種偷懶的做法。
2. 乙個類中可以存在多個拷貝建構函式,例如:
calss a
暫時就先分析到這裡,如果以後遇到新的關於拷貝構造的情況,會繼續分析。
對深拷貝與淺拷貝的再次理解
先說下自己的理解吧,淺拷貝,即在定義乙個類a 使用類似 a obj a obj1 obj 或者a obj1 obj 時候,由於沒有自定義拷貝建構函式,c 編譯器自動會產生乙個預設的拷貝建構函式。這個預設的拷貝建構函式採用的是 位拷貝 淺拷貝 而非 值拷貝 深拷貝 的方式,如果類中含有指標變數,預設的...
淺拷貝 深拷貝的再次理解
以前對淺拷貝的認識不夠,認為只是對指標的賦值,沒有真實的開闢空間。看了一篇部落格後感覺自己的認識還不夠,其實淺拷貝還會導致記憶體洩漏!對,你沒看錯,確實是 記憶體洩漏!最後會把部落格鏈結貼出來。我們以前對淺拷貝深拷貝的理解是這樣的 淺拷貝是對指標的拷貝,拷貝以後兩個指標指向的是同一塊記憶體空間,深拷...
Python 淺拷貝與深拷貝 再次整理
coding utf 8 import copy def print id lst lst id id y for y in lst lst id.extend id y for y in lst 1 return str lst id def test copy anndy anndy age 2...