C++中一般創建對象,拷貝或賦值的方式有構造函數,拷貝構造函數,賦值函數這三種方法。下面就詳細比較下三者之間的區別以及它們的具體實現
1.構造函數
構造函數是一種特殊的類成員函數,是當創建一個類的對象時,它被調用來對類的數據成員進行初始化和分配內存。(構造函數的命名必須和類名完全相同)
首先說一下一個C++的空類,編譯器會加入哪些默認的成員函數
·默認構造函數和拷貝構造函數
·析構函數
·賦值函數(賦值運算符)
·取值函數
**即使程序沒定義任何成員,編譯器也會插入以上的函數!
注意:構造函數可以被重載,可以多個,可以帶參數;
析構函數只有一個,不能被重載,不帶參數
而默認構造函數沒有參數,它什麼也不做。當沒有重載無參構造函數時,
A a就是通過默認構造函數來創建一個對象
下面代碼爲構造函數重載的實現
2.拷貝構造函數
拷貝構造函數是C++獨有的,它是一種特殊的構造函數,用基於同一類的一個對象構造和初始化另一個對象。
當沒有重載拷貝構造函數時,通過默認拷貝構造函數來創建一個對象
A a;
A b(a);
A b=a; 都是拷貝構造函數來創建對象b
強調:這裏b對象是不存在的,是用a 對象來構造和初始化b的!!
先說下什麼時候拷貝構造函數會被調用:
在C++中,3種對象需要複製,此時拷貝構造函數會被調用
1)一個對象以值傳遞的方式傳入函數體
2)一個對象以值傳遞的方式從函數返回
3)一個對象需要通過另一個對象進行初始化
什麼時候編譯器會生成默認的拷貝構造函數:
1)如果用戶沒有自定義拷貝構造函數,並且在代碼中使用到了拷貝構造函數,編譯器就會生成默認的拷貝構造函數。但如果用戶定義了拷貝構造函數,編譯器就不在生成。
2)如果用戶定義了一個構造函數,但不是拷貝構造函數,而此時代碼中又用到了拷貝構造函數,那編譯器也會生成默認的拷貝構造函數。
因爲系統提供的默認拷貝構造函數工作方式是內存拷貝,也就是淺拷貝。如果對象中用到了需要手動釋放的對象,則會出現問題,這時就要手動重載拷貝構造函數,實現深拷貝。
下面說說深拷貝與淺拷貝:
淺拷貝:如果複製的對象中引用了一個外部內容(例如分配在堆上的數據),那麼在複製這個對象的時候,讓新舊兩個對象指向同一個外部內容,就是淺拷貝。(指針雖然複製了,但所指向的空間內容並沒有複製,而是由兩個對象共用,兩個對象不獨立,刪除空間存在)
深拷貝:如果在複製這個對象的時候爲新對象製作了外部對象的獨立複製,就是深拷貝。
拷貝構造函數重載聲明如下:
A (const A&other)
下面爲拷貝構造函數的實現:
3.賦值函數
當一個類的對象向該類的另一個對象賦值時,就會用到該類的賦值函數。
當沒有重載賦值函數(賦值運算符)時,通過默認賦值函數來進行賦值操作
A a;
A b;
b=a;
強調:這裏a,b對象是已經存在的,是用a 對象來賦值給b的!!
賦值運算的重載聲明如下:
A& operator = (const A& other)
通常大家會對拷貝構造函數和賦值函數混淆,這兒仔細比較兩者的區別:
1)拷貝構造函數是一個對象初始化一塊內存區域,這塊內存就是新對象的內存區,而賦值函數是對於一個已經被初始化的對象來進行賦值操作。
2)一般來說在數據成員包含指針對象的時候,需要考慮兩種不同的處理需求:一種是複製指針對象,另一種是引用指針對象。拷貝構造函數大多數情況下是複製,而賦值函數是引用對象
3)實現不一樣。拷貝構造函數首先是一個構造函數,它調用時候是通過參數的對象初始化產生一個對象。賦值函數則是把一個新的對象賦值給一個原有的對象,所以如果原來的對象中有內存分配要先把內存釋放掉,而且還要檢察一下兩個對象是不是同一個對象,如果是,不做任何操作,直接返回。(這些要點會在下面的String實現代碼中體現)
!!!如果不想寫拷貝構造函數和賦值函數,又不允許別人使用編譯器生成的缺省函數,最簡單的辦法是將拷貝構造函數和賦值函數聲明爲私有函數,不用編寫代碼。如:
如果程序這樣寫就會出錯:
所以如果類定義中有指針或引用變量或對象,爲了避免潛在錯誤,最好重載拷貝構造函數和賦值函數。
下面以string類的實現爲例,完整的寫了普通構造函數,拷貝構造函數,賦值函數的實現。String類的基本實現見我另一篇博文。
一句話記住三者:對象不存在,且沒用別的對象來初始化,就是調用了構造函數;
對象不存在,且用別的對象來初始化,就是拷貝構造函數(上面說了三種用它的情況!)
對象存在,用別的對象來給它賦值,就是賦值函數。
以上爲本人結合很多資料和圖書整理出來的,將核心的點都系統的理出來,全自己按條理寫的,現在大家對普通構造函數,拷貝構造函數,賦值函數的區別和實現應該都清楚了。
C++構造函數、拷貝構造函數、賦值函數淺析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.