「淺拷貝」與「深拷貝」

2021-06-22 13:18:19 字數 3489 閱讀 8889

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各自指向一段記憶體空間,但它們指向的空間具有相同的內容,這就是所謂的「深拷貝」。

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

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

淺拷貝與深拷貝

淺拷貝 1 2 myclass a,b a b 為了封裝性和解耦,同型別的兩個物件之間進行賦值操作時,所有成員變數被複製,包括私有成員 指標變數。類的成員函式在傳遞或返回物件時都會進行物件複製產生臨時物件,比如函式呼叫時實參變為形參,以及函式返回物件。考慮到效能和使用者要求不同,編譯器不複製物件內部...

「淺拷貝」與「深拷貝」

c 中物件的複製就如同 轉殖 用乙個已有的物件快速地複製出多個完全相同的物件。一般而言,以下三種情況都會使用到物件的複製 1 建立乙個新物件,並用另乙個同類的已有物件對新物件進行初始化,例如 cpp view plain copy class rect rect rect1 rect rect2 r...

深拷貝與淺拷貝

c 中物件的複製就如同 轉殖 用乙個已有的物件快速地複製出多個完全相同的物件。一般而言,以下三種情況都會使用到物件的複製 1 建立乙個新物件,並用另乙個同類的已有物件對新物件進行初始化,例如 class rect rect rect1 rect rect2 rect1 使用rect1初始化rect2...