淺拷貝和深拷貝

2021-06-27 02:03:08 字數 3546 閱讀 4865

c++中物件的複製就如同「轉殖」,用乙個已有的物件快速地複製出多個完全相同的物件。一般而言,以下三種情況都會使用到物件的複製:

(1)建立乙個新物件,並用另乙個同類的已有物件對新物件進行初始化,例如:

[cpp]view plain

copy

class rect  

;  rect rect1;  

rect rect2(rect1);  // 使用rect1初始化rect2,此時會進行物件的複製

(2)當函式的引數為類的物件時,這時呼叫此函式時使用的是值傳遞,也會產生物件的複製,例如:

[cpp]view plain

copy

void fun1(rect rect)  

int main()    

(3)函式的返回值是類的物件時,在函式呼叫結束時,需要將函式中的物件複製乙個臨時物件並傳給改函式的呼叫處,例如:

[cpp]view plain

copy

rect fun2()  

int main()    

物件的複製都是通過一種特殊的建構函式來完成的,這種特殊的建構函式就是拷貝建構函式(copy constructor,也叫複製建構函式)。拷貝建構函式在大多數情況下都很簡單,甚至在我們都不知道它存在的情況下也能很好發揮作用,但是在一些特殊情況下,特別是在物件裡有動態成員的時候,就需要我們特別小心地處理拷貝建構函式了。下面我們就來看看拷貝建構函式的使用。

一、預設拷貝建構函式

很多時候在我們都不知道拷貝建構函式的情況下,傳遞物件給函式引數或者函式返回物件都能很好的進行,這是因為編譯器會給我們自動產生乙個拷貝建構函式,這就是「預設拷貝建構函式」,這個建構函式很簡單,僅僅使用「老物件」的資料成員的值對「新物件」的資料成員一一進行賦值,它一般具有以下形式:

[cpp]view plain

copy

rect::rect(

const rect& r)    

[cpp]view plain

copy

class rect  

~rect()     // 析構函式,計數器減1

static

int getcount()       // 返回計數器的值

private:  

int width;  

int height;  

static

int count;       // 一靜態成員做為計數器

};  

int rect::count = 0;        

// 初始化計數器

int main()    

這段**對前面的類進行了一下小小的修改,加入了乙個靜態成員,目的是進行計數,統計建立的物件的個數,在每個物件建立時,通過建構函式進行遞增,在銷毀物件時,通過析構函式進行遞減。在主函式中,首先建立物件rect1,輸出此時的物件個數,然後使用rect1複製出物件rect2,再輸出此時的物件個數,按照理解,此時應該有兩個物件存在,但實際程式執行時,輸出的都是1,反應出只有1個物件。此外,在銷毀物件時,由於會呼叫銷毀兩個物件,類的析構函式會呼叫兩次,此時的計數器將變為負數。出現這些問題最根本就在於在複製物件時,計數器沒有遞增,解決的辦法就是重新編寫拷貝建構函式,在拷貝建構函式中加入對計數器的處理,形成的拷貝建構函式如下:

[cpp]view plain

copy

class rect  

rect(const rect& r)   

// 拷貝建構函式

~rect()     // 析構函式,計數器減1

static

int getcount()   // 返回計數器的值

private:  

int width;  

int height;  

static

int count;       // 一靜態成員做為計數器

};  

自己編寫拷貝建構函式又可以分為兩種情況——淺拷貝與深拷貝。

二、淺拷貝

所謂淺拷貝,指的是在物件複製時,只是對物件中的資料成員進行簡單的賦值,上面的例子都是屬於淺拷貝的情況,預設拷貝建構函式執行的也是淺拷貝。大多情況下「淺拷貝」已經能很好地工作了,但是一旦物件存在了動態成員,那麼淺拷貝就會出問題了,讓我們考慮如下一段**:

[cpp]view plain

copy

class rect  

~rect()     // 析構函式,釋放動態分配的空間

}  private:  

int width;  

int height;  

int *p;     

// 一指標成員

};  

int main()    

在這段**執行結束之前,會出現乙個執行錯誤。原因就在於在進行物件複製時,對於動態分配的內容沒有進行正確的操作。我們來分析一下:

在執行定義rect1物件後,由於在建構函式中有乙個動態分配的語句,因此執行後的記憶體情況大致如下:

在使用rect1複製rect2時,由於執行的是淺拷貝,只是將成員的值進行賦值,所以此時rect1.p和rect2.p具有相同的值,也即這兩個指標指向了堆裡的同乙個空間,如下圖所示:

當然,這不是我們所期望的結果,在銷毀物件時,兩個物件的析構函式將對同乙個記憶體空間釋放兩次,這就是錯誤出現的原因。我們需要的不是兩個p有相同的值,而是兩個p指向的空間有相同的值,解決辦法就是使用「深拷貝」。

三、深拷貝

在「深拷貝」的情況下,對於物件中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間,如上面的例子就應該按照如下的方式進行處理:

[cpp]view plain

copy

class rect  

rect(const rect& r)  

~rect()     // 析構函式,釋放動態分配的空間

}  private:  

int width;  

int height;  

int *p;     

// 一指標成員

};  

此時,在完成物件的複製後,記憶體的乙個大致情況如下:

此時rect1的p和rect2的p各自指向一段記憶體空間,但它們指向的空間具有相同的內容,這就是所謂的「深拷貝」。

此外,在與「物件的複製」很類似的「物件的賦值」的情況下,也會出現同樣的問題。在「物件的賦值」一文中再來討論此問題。

通過對物件複製的分析,我們發現物件的複製大多在進行「值傳遞」時發生,這裡有乙個小技巧可以防止按值傳遞——宣告乙個私有拷貝建構函式。甚至不必去定義這個拷貝建構函式,這樣因為拷貝建構函式是私有的,如果使用者試圖按值傳遞或函式返回該類物件,將得到乙個編譯錯誤,從而可以避免按值傳遞或返回物件。

《c++程式設計》  譚浩強

《c++程式設計思想 第1卷》   bruce eckel

深拷貝和淺拷貝

淺拷貝就是物件的資料成員之間的簡單賦值,如你設計了乙個沒有類而沒有提供它的複製建構函式,當用該類的乙個物件去給令乙個物件賦值時所執行的過程就是淺拷貝,如 class a a private int data int main 這一句b a 就是淺拷貝,執行完這句後b.data 5 如果物件中沒有其他...

淺拷貝和深拷貝

以下情況都會呼叫拷貝建構函式 乙個物件以值傳遞的方式傳入函式體 例如 已知class a,class b void func a a void func a a func b b 此時函式對b的操作是呼叫拷貝建構函式後的臨時拷貝物件。多數傳指標 乙個物件以值傳遞的方式從函式返回 如 return b...

深拷貝和淺拷貝

ios提供了copy和mutablecopy方法,顧名思義,copy就是複製了乙個imutable的物件,而mutablecopy就是複製了乙個mutable的物件。以下將舉幾個例子來說明。1 系統的非容器類物件 這裡指的是nsstring nsnumber等等一類的物件。nsstring stri...