原創 漫談C 深淺拷貝

2022-04-09 07:32:41 字數 3032 閱讀 3573

對於一般的物件,如:

int a = 10

;int b = 20;

它們之間的賦值、複製過程是很簡單的。但是對於類物件來說,其內部存在各種型別成員變數,在拷貝過程中會出現問題。如下:

1 #include2 #include3

using

namespace

std;

4class

string

9 ~string ()

14 cout << "

string析構

編譯通過了,執行後出現一堆的錯誤,為什麼?!這就是淺拷貝帶來的問題。

事實是,在物件拷貝過程中,如果沒有自定義拷貝建構函式,系統會提供乙個預設的拷貝建構函式,預設的拷貝建構函式對於基本型別的成員變數,按位元組複製,對於類型別成員變數,呼叫其相應型別的拷貝建構函式。原型如下:

string (const string& that) {}
但凡是編譯系統提供的預設函式,總不是十全十美的。

預設拷貝建構函式在拷貝過程中是按位元組複製的,對於指標型成員變數只複製指標本身,而不複製指標所指向的目標----淺拷貝。

用下圖來解釋這個問題:

在進行物件複製後,事實上s1,s2裡的成員指標m_psz都指向了一塊記憶體空間(即記憶體空間共享了),在s1析構時,delete了成員指標m_psz所指向的記憶體空間,而s2析構時同樣指向(此時已變成野指標)並且要釋放這片已經被s1析構函式釋放的記憶體空間,這就讓同樣一片記憶體空間出現了「double free」 ,從而出錯。而淺拷貝還存在著乙個問題,因為一片空間被兩個不同的子物件共享了,只要其中的乙個子物件改變了其中的值,那另乙個物件的值也跟著改變了,正如程式中只改變了s1.c_str()[0] = 'h',然而輸出的s1,s2均為

hello,所以這並不是真正意義上的複製。

為了實現深拷貝,往往需要自己定義拷貝建構函式,在源**裡,我們加入自定義的拷貝建構函式如下:

string (const string& that) : m_psz(strcpy((new

char[strlen(that.m_psz)+1

]),that.m_psz))

這樣再執行就沒有問題了。

在程式中,還有哪些情況會用到拷貝建構函式呢?當函式存在物件型的引數或物件型的返回值時都會用到拷貝建構函式。

而拷貝賦值的情況基本上與拷貝複製是一樣的。只是拷貝賦值是屬於操作符過載問題。例如在主函式若有:string s3;s3 = s2;這樣系統在執行時會呼叫系統提供的預設的拷貝賦值函式,原型如下:

void

operator = (const string& that) {}

我們可以自定義拷貝賦值函式如下:

void

operator=(const string&that)

但是這只是新手級別的寫法,考慮的問題太少。我們知道對於普通變數來講a=b返回的是左值a的引用,所以它可以作為左值繼續接收其他值(a=b)=30,這樣來講我們操作符過載後返回的應該是類物件的引用(否則返回值將不能作為左值來進行運算),如下:

string& operator=(const string&that)
而 m_psz = strcpy (new

char[strlen(that.m_psz)+1

],that.m_psz);這種寫法其實也有問題,因為在執行語句時,m_psz已經被構造已經分配了記憶體空間,但是如此進行指標賦值,m_psz直接轉而指向另一片新new出來的記憶體空間,而丟棄了原來的記憶體,這樣便造成了記憶體洩露。應更改為:

string& operator=(const string&that)
這樣就行了嗎?在這個世界上不怕沒好事就怕沒好人,萬一他跟你搞乙個自賦值(s3=s3)怎麼辦?

操作符左右兩邊都是同乙個物件,這樣先delete m_psz,後面又有that.m_psz,這就出現了問題。所以為了防止自賦值,我們一般的寫法為:

string& operator=(cosnt string&that) 

return *this

;}

可是這樣寫就完善了嗎?是否要再仔細思索一下,還存在問題嗎?!其實我可以告訴你,這樣的寫法也頂多算個初級工程師的寫法。前面說過,為了保證記憶體不洩露,我們前面delete m_psz,然後我們在把new出來的空間給了m_psz,但是這樣的問題是,你有考慮過萬一new失敗了呢?!記憶體分配失敗,m_psz沒有指向新的記憶體空間,但是它卻已經把舊的空間給扔掉了,所以顯然這樣的寫法依舊存在著問題。一般高階工程師的寫法會是這樣的:

1 string& operator=(cosnt string&that) 

7return *this

;8 }

這樣考慮的問題便比較全面了。

高階工程師高確實高,然而有沒有比高階工程師更高的工程師呢?答案是肯定的。對於從事多年c++開發元老級別資深的c++工程師來講,他們不會這麼寫,因為有更好更簡便的寫法,如下:

1 string& operator=(const string&that) 

8return *this

;9 }

有人看出來這樣寫的玄機了嗎??

事實上,這是借助了以上自定義的拷貝建構函式。定義了區域性物件str,在拷貝構造中已經為str的成員指標分配了一塊記憶體,所以只需要將str.m_psz與this->m_psz交換指標即可,簡化了程式的設計,因為str是區域性物件,離開作用域會呼叫析構函式釋放交換給str.m_psz的記憶體,避免了記憶體洩露。

C 深淺拷貝

當結構體中沒有指標時,可進行淺拷貝,資料也會從乙個結構體拷貝到另乙個結構體 兩個結構體都存乙份資料 但當結構體中有指標的時候,假如使用使用淺拷貝,會使兩個結構體使用的指標都指向同乙個記憶體位址,在析構的時候會造成記憶體洩漏。深拷貝 需要對含有指標的結構體,使用 new 申請新的記憶體空間去儲存拷貝的...

c 深淺拷貝

對於普通型別的物件來說,它們之間的複製是很簡單的,例如 int a 88 int b a 而類物件與普通物件不同,類物件內部結構一般較為複雜,存在各種成員變數。下面看乙個類物件拷貝的簡單例子。執行程式,螢幕輸出100。從以上 的執行結果可以看出,系統為物件b分配了記憶體並完成了與物件a的複製過程。就...

C 什麼是深淺拷貝,深淺拷貝的區別?

淺拷貝 class string string const string s str s.str string operator const string s 返回引用是為了連續的賦值 return this string 防止野指標的出現 str null char str get void st...