對於普通的類型來說,拷貝沒什麼大不了的。
int a = 0;int b = a;
不會出現任何問題。
而類對象與普通對象不同,類對象內部結構一般較為複雜,存在各種成員變數。
淺拷貝
首先來說說我們常遇到的淺拷貝的情況。
#include <stdio.h> class student{public: student() // 建構函式,p指向堆中分配的一空間 { _name = new char(100); printf("預設建構函式\n"); } ~student() // 解構函式,釋放動態分配的空間 { if (_name != NULL) { delete _name; _name = NULL; printf("解構函式\n"); } }private: char * _name; // 一指標成員};int main(){ student a; student b(a); // 複製對象 return 0;}
這段代碼乍看之下沒什麼毛病,通過類的預設建構函式將 a 複製給 b ,但是一旦運行就會程式崩潰。
經過我的刻苦學習與鑽研,終於發現其中的問題所在。
由於我的類沒有拷貝建構函式,所以student b(a)
會調用,編譯器自動產生的一個預設拷貝建構函式,該建構函式完成對象之間的位拷貝。位拷貝又稱淺拷貝。
淺拷貝:
淺拷貝只是拷貝了指標,並沒有建立新的空間,使得兩個指標指向同一個地址,這樣在對象塊結束,調用函數析構的時,會造成同一份資源析構2次,即delete同一塊記憶體2次,造成程式崩潰。
淺拷貝使得 a 和 b 指向同一塊記憶體,任何一方的變動都會影響到另一方。
由於 a 和 b 指向的是同一塊記憶體空間,當 a 釋放了後,b 指向的記憶體空間不複存在,所以會出現記憶體泄露的情況。
如何避免淺拷貝害人呢?
養成自訂拷貝建構函式的習慣,當顯式定義了拷貝建構函式後,編譯器就會調用拷貝建構函式了,為了不出現程式崩潰,請使用自訂拷貝建構函式,當然我們自己如果把代碼寫成了淺拷貝的形式,那也不是不可能的事。
深拷貝
// 使用自定製拷貝建構函式,完成深拷貝!!!class A{public: A() // 建構函式,p指向堆中分配的一空間 { m_pdata = new char(100); printf("預設建構函式\n"); } A(const A& r) // 拷貝建構函式 { m_pdata = new char(100); // 為新對象重新動態分配空間 memcpy(m_pdata, r.m_pdata, strlen(r.m_pdata)); printf("copy建構函式\n"); } ~A() // 解構函式,釋放動態分配的空間 { if (m_pdata != NULL) { delete m_pdata; printf("解構函式\n"); } }private: char *m_pdata; // 一指標成員};int main(){ A a; A b(a); // 複製對象 return 0;}
在拷貝建構函式中,為 b 對象 new 了一個新的空間,這樣 a 和 b 指向的是不同的空間,只是內容一致,但是互不影響。
重複的去開闢空間和釋放空間效率是很低的,聰明的地球人決定使用寫時拷貝。
寫時拷貝
寫時拷貝:引入一個計數器,每片不同內容的空間上都再由一個計數器組成,在構造第一個類指向時,計數器初始化為1,之後每次有新的類也指向同一片空間時,計數器加 1 ;在析構時判斷該片空間對應計數器是否為1,為1則執行清理工作,大於1則計數器減 1 。如果有需要進行增刪等操作時,再拷貝空間完成,有利於提高效率。
class String{public: String(const char* str = "") :_str(new char[strlen(str) + 1 + 4])//+1表示字串後面要放一個'\0',+4表示多開闢一個空間存放引用計數 { _str += 4;//_str指向資料存放區 strcpy(_str, str); _GetCount() = 1; } String(const String& s) :_str(s._str) { _GetCount()++; } String& operator=(String& s) { if (this != &s) { if (--_GetCount() == 0) { delete[](_str - 4); } ++s._GetCount(); _str = s._str; } return *this; } ~String() { if (--_GetCount() == 0) { delete[](_str - 4); // 注意:由於計數器存放在了_str首地址-4的地址上,所以在析構時一定要注意全部釋放,避免記憶體流失。 } }public: int& _GetCount() { return *((int*)_str - 1); }private: char* _str;};
相關文章:
C#淺拷貝和深拷貝執行個體解析
Python中的賦值、淺拷貝、深拷貝介紹