[016]轉--C++拷貝構造函數詳解

一. 什么是拷貝構造函數

首先對于普通類型的對象來說,它們之間的復制是很簡單的,例如:

[c-sharp]?view plaincopy
  1. int?a?=?100;??
  2. int?b?=?a; ??

而類對象與普通對象不同,類對象內部結構一般較為復雜,存在各種成員變量。
下面看一個類對象拷貝的簡單例子。

[c-sharp]?view plaincopy
  1. #include?<iostream>??
  2. using?namespace?std;??
  3. ??
  4. class?CExample?{??
  5. private:??
  6. ???? int?a;??
  7. public:??
  8. ??????//構造函數??
  9. ???? CExample(int?b)??
  10. ???? {?a?=?b;}??
  11. ??
  12. ??????//一般函數??
  13. ???? void?Show?()??
  14. ???? {??
  15. ????????cout<<a<<endl;??
  16. ??????}??
  17. };??
  18. ??
  19. int?main()??
  20. {??
  21. ???? CExample?A(100);??
  22. ???? CExample?B?=?A;?//注意這里的對象初始化要調用拷貝構造函數,而非賦值??
  23. ???? ?B.Show?();??
  24. ???? return?0;??
  25. } ?

運行程序,屏幕輸出100。從以上代碼的運行結果可以看出,系統為對象 B 分配了內存并完成了與對象 A 的復制過程。就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的。

下面舉例說明拷貝構造函數的工作過程。

[c-sharp]?view plaincopy
  1. #include?<iostream>??
  2. using?namespace?std;??
  3. ??
  4. class?CExample?{??
  5. private:??
  6. ????int?a;??
  7. public:??
  8. ????//構造函數??
  9. ????CExample(int?b)??
  10. ????{?a?=?b;}??
  11. ??????
  12. ????//拷貝構造函數??
  13. ????CExample(const?CExample&?C)??
  14. ????{??
  15. ????????a?=?C.a;??
  16. ????}??
  17. ??
  18. ????//一般函數??
  19. ????void?Show?()??
  20. ????{??
  21. ????????cout<<a<<endl;??
  22. ????}??
  23. };??
  24. ??
  25. int?main()??
  26. {??
  27. ????CExample?A(100);??
  28. ????CExample?B?=?A;?//?CExample?B(A);?也是一樣的??
  29. ?????B.Show?();??
  30. ????return?0;??
  31. } ??

CExample(const CExample& C) 就是我們自定義的拷貝構造函數。可見,拷貝構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它必須的一個參數是本類型的一個引用變量

?

二. 拷貝構造函數的調用時機

在C++中,下面三種對象需要調用拷貝構造函數!
1. 對象以值傳遞的方式傳入函數參數

[c-sharp]?view plaincopy
  1. class?CExample???
  2. {??
  3. private:??
  4. ?int?a;??
  5. ??
  6. public:??
  7. ?//構造函數??
  8. ?CExample(int?b)??
  9. ?{???
  10. ??a?=?b;??
  11. ??cout<<"creat:?"<<a<<endl;??
  12. ?}??
  13. ??
  14. ?//拷貝構造??
  15. ?CExample(const?CExample&?C)??
  16. ?{??
  17. ??a?=?C.a;??
  18. ??cout<<"copy"<<endl;??
  19. ?}??
  20. ???
  21. ?//析構函數??
  22. ?~CExample()??
  23. ?{??
  24. ??cout<<?"delete:?"<<a<<endl;??
  25. ?}??
  26. ??
  27. ?????void?Show?()??
  28. ?{??
  29. ?????????cout<<a<<endl;??
  30. ?????}??
  31. };??
  32. ??
  33. //全局函數,傳入的是對象??
  34. void?g_Fun(CExample?C)??
  35. {??
  36. ?cout<<"test"<<endl;??
  37. }??
  38. ??
  39. int?main()??
  40. {??
  41. ?CExample?test(1);??
  42. ?//傳入對象??
  43. ?g_Fun(test);??
  44. ??
  45. ?return?0;??
  46. } ?

調用g_Fun()時,會產生以下幾個重要步驟:
(1).test對象傳入形參時,會先會產生一個臨時變量,就叫 C 吧。
(2).然后調用拷貝構造函數把test的值給C。 整個這兩個步驟有點像:CExample C(test);
(3).等g_Fun()執行完后, 析構掉 C 對象。

?

2. 對象以值傳遞的方式從函數返回

[c-sharp]?view plaincopy
  1. class?CExample???
  2. {??
  3. private:??
  4. ?int?a;??
  5. ??
  6. public:??
  7. ?//構造函數??
  8. ?CExample(int?b)??
  9. ?{???
  10. ??a?=?b;??
  11. ?}??
  12. ??
  13. ?//拷貝構造??
  14. ?CExample(const?CExample&?C)??
  15. ?{??
  16. ??a?=?C.a;??
  17. ??cout<<"copy"<<endl;??
  18. ?}??
  19. ??
  20. ?????void?Show?()??
  21. ?????{??
  22. ?????????cout<<a<<endl;??
  23. ?????}??
  24. };??
  25. ??
  26. //全局函數??
  27. CExample?g_Fun()??
  28. {??
  29. ?CExample?temp(0);??
  30. ?return?temp;??
  31. }??
  32. ??
  33. int?main()??
  34. {??
  35. ?g_Fun();??
  36. ?return?0;??
  37. } ?

當g_Fun()函數執行到return時,會產生以下幾個重要步驟:
(1). 先會產生一個臨時變量,就叫XXXX吧。
(2). 然后調用拷貝構造函數把temp的值給XXXX。整個這兩個步驟有點像:CExample XXXX(temp);
(3). 在函數執行到最后先析構temp局部變量。
(4). 等g_Fun()執行完后再析構掉XXXX對象。

?

3. 對象需要通過另外一個對象進行初始化;

[c-sharp]?view plaincopy
  1. CExample?A(100);??
  2. CExample?B?=?A;???
  3. //?CExample?B(A); ??

后兩句都會調用拷貝構造函數。

?

三. 淺拷貝和深拷貝

1. 默認拷貝構造函數

??? 很多時候在我們都不知道拷貝構造函數的情況下,傳遞對象給函數參數或者函數返回對象都能很好的進行,這是因為編譯器會給我們自動產生一個拷貝構造函數,這就是“默認拷貝構造函數”,這個構造函數很簡單,僅僅使用“老對象”的數據成員的值對“新對象”的數據成員一一進行賦值,它一般具有以下形式:

?

[c-sharp]?view plaincopy
  1. Rect::Rect(const?Rect&?r)??
  2. {??
  3. ????width?=?r.width;??
  4. ????height?=?r.height;??
  5. }??

?
??? 當然,以上代碼不用我們編寫,編譯器會為我們自動生成。但是如果認為這樣就可以解決對象的復制問題,那就錯了,讓我們來考慮以下一段代碼:

[c-sharp]?view plaincopy
  1. class?Rect??
  2. {??
  3. public:??
  4. ????Rect()??????//?構造函數,計數器加1??
  5. ????{??
  6. ????????count++;??
  7. ????}??
  8. ????~Rect()?????//?析構函數,計數器減1??
  9. ????{??
  10. ????????count--;??
  11. ????}??
  12. ????static?int?getCount()???????//?返回計數器的值??
  13. ????{??
  14. ????????return?count;??
  15. ????}??
  16. private:??
  17. ????int?width;??
  18. ????int?height;??
  19. ????static?int?count;???????//?一靜態成員做為計數器??
  20. };??
  21. ??
  22. int?Rect::count?=?0;????????//?初始化計數器??
  23. ??
  24. int?main()??
  25. {??
  26. ????Rect?rect1;??
  27. ????cout<<"The?count?of?Rect:?"<<Rect::getCount()<<endl;??
  28. ??
  29. ????Rect?rect2(rect1);???//?使用rect1復制rect2,此時應該有兩個對象??
  30. ?????cout<<"The?count?of?Rect:?"<<Rect::getCount()<<endl;??
  31. ??
  32. ????return?0;??
  33. }??

?

  這段代碼對前面的類,加入了一個靜態成員,目的是進行計數。在主函數中,首先創建對象rect1,輸出此時的對象個數,然后使用rect1復制出對象rect2,再輸出此時的對象個數,按照理解,此時應該有兩個對象存在,但實際程序運行時,輸出的都是1,反應出只有1個對象。此外,在銷毀對象時,由于會調用銷毀兩個對象,類的析構函數會調用兩次,此時的計數器將變為負數。

說白了,就是拷貝構造函數沒有處理靜態數據成員。

出現這些問題最根本就在于在復制對象時,計數器沒有遞增,我們重新編寫拷貝構造函數,如下:

[c-sharp]?view plaincopy
  1. class?Rect??
  2. {??
  3. public:??
  4. ????Rect()??????//?構造函數,計數器加1??
  5. ????{??
  6. ????????count++;??
  7. ????}??
  8. ????Rect(const?Rect&?r)???//?拷貝構造函數??
  9. ????{??
  10. ????????width?=?r.width;??
  11. ????????height?=?r.height;??
  12. ????????count++;??????????//?計數器加1??
  13. ????}??
  14. ????~Rect()?????//?析構函數,計數器減1??
  15. ????{??
  16. ????????count--;??
  17. ????}??
  18. ????static?int?getCount()???//?返回計數器的值??
  19. ????{??
  20. ????????return?count;??
  21. ????}??
  22. private:??
  23. ????int?width;??
  24. ????int?height;??
  25. ????static?int?count;???????//?一靜態成員做為計數器??
  26. };??

?

2. 淺拷貝

??? 所謂淺拷貝,指的是在對象復制時,只對對象中的數據成員進行簡單的賦值,默認拷貝構造函數執行的也是淺拷貝。大多情況下“淺拷貝”已經能很好地工作了,但是一旦對象存在了動態成員,那么淺拷貝就會出問題了,讓我們考慮如下一段代碼:

[c-sharp]?view plaincopy
  1. class?Rect??
  2. {??
  3. public:??
  4. ????Rect()??????//?構造函數,p指向堆中分配的一空間??
  5. ????{??
  6. ????????p?=?new?int(100);??
  7. ????}??
  8. ????~Rect()?????//?析構函數,釋放動態分配的空間??
  9. ????{??
  10. ????????if(p?!=?NULL)??
  11. ????????{??
  12. ????????????delete?p;??
  13. ????????}??
  14. ????}??
  15. private:??
  16. ????int?width;??
  17. ????int?height;??
  18. ????int?*p;?????//?一指針成員??
  19. };??
  20. ??
  21. int?main()??
  22. {??
  23. ????Rect?rect1;??
  24. ????Rect?rect2(rect1);???//?復制對象??
  25. ????return?0;??
  26. }??

?

??? 在這段代碼運行結束之前,會出現一個運行錯誤。原因就在于在進行對象復制時,對于動態分配的內容沒有進行正確的操作。我們來分析一下:

??? 在運行定義rect1對象后,由于在構造函數中有一個動態分配的語句,因此執行后的內存情況大致如下:

?

?

??? 在使用rect1復制rect2時,由于執行的是淺拷貝,只是將成員的值進行賦值,這時?rect1.p?= rect2.p,也即這兩個指針指向了堆里的同一個空間,如下圖所示:

?

當然,這不是我們所期望的結果,在銷毀對象時,兩個對象的析構函數將對同一個內存空間釋放兩次,這就是錯誤出現的原因。我們需要的不是兩個p有相同的值,而是兩個p指向的空間有相同的值,解決辦法就是使用“深拷貝”。


3. 深拷貝

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

[c-sharp]?view plaincopy
  1. class?Rect??
  2. {??
  3. public:??
  4. ????Rect()??????//?構造函數,p指向堆中分配的一空間??
  5. ????{??
  6. ????????p?=?new?int(100);??
  7. ????}??
  8. ????Rect(const?Rect&?r)??
  9. ????{??
  10. ????????width?=?r.width;??
  11. ????????height?=?r.height;??
  12. ????????p?=?new?int;????//?為新對象重新動態分配空間??
  13. ????????*p?=?*(r.p);??
  14. ????}??
  15. ????~Rect()?????//?析構函數,釋放動態分配的空間??
  16. ????{??
  17. ????????if(p?!=?NULL)??
  18. ????????{??
  19. ????????????delete?p;??
  20. ????????}??
  21. ????}??
  22. private:??
  23. ????int?width;??
  24. ????int?height;??
  25. ????int?*p;?????//?一指針成員??
  26. };??

?

此時,在完成對象的復制后,內存的一個大致情況如下:

?

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

?


3. 防止默認拷貝發生

??? 通過對對象復制的分析,我們發現對象的復制大多在進行“值傳遞”時發生,這里有一個小技巧可以防止按值傳遞——聲明一個私有拷貝構造函數。甚至不必去定義這個拷貝構造函數,這樣因為拷貝構造函數是私有的,如果用戶試圖按值傳遞或函數返回該類對象,將得到一個編譯錯誤,從而可以避免按值傳遞或返回對象。

[c-sharp]?view plaincopy
  1. //?防止按值傳遞??
  2. class?CExample???
  3. {??
  4. private:??
  5. ????int?a;??
  6. ??
  7. public:??
  8. ????//構造函數??
  9. ????CExample(int?b)??
  10. ????{???
  11. ????????a?=?b;??
  12. ????????cout<<"creat:?"<<a<<endl;??
  13. ????}??
  14. ??
  15. private:??
  16. ????//拷貝構造,只是聲明??
  17. ????CExample(const?CExample&?C);??
  18. ??
  19. public:??
  20. ????~CExample()??
  21. ????{??
  22. ????????cout<<?"delete:?"<<a<<endl;??
  23. ????}??
  24. ??
  25. ????void?Show?()??
  26. ????{??
  27. ????????cout<<a<<endl;??
  28. ????}??
  29. };??
  30. ??
  31. //全局函數??
  32. void?g_Fun(CExample?C)??
  33. {??
  34. ????cout<<"test"<<endl;??
  35. }??
  36. ??
  37. int?main()??
  38. {??
  39. ????CExample?test(1);??
  40. ????//g_Fun(test);?按值傳遞將出錯??
  41. ??????
  42. ????return?0;??
  43. }???

?

?

四. 拷貝構造函數的幾個細節

1. 拷貝構造函數里能調用private成員變量嗎?
解答:
這個問題是在網上見的,當時一下子有點暈。其時從名子我們就知道拷貝構造函數其時就是一個特殊的構造函數,操作的還是自己類的成員變量,所以不受private的限制。

2. 以下函數哪個是拷貝構造函數,為什么?

[c-sharp]?view plaincopy
  1. X::X(const?X&);??????
  2. X::X(X);??????
  3. X::X(X&,?int?a=1);??????
  4. X::X(X&,?int?a=1,?int?b=2);??


解答:對于一個類X, 如果一個構造函數的第一個參數是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且沒有其他參數或其他參數都有默認值,那么這個函數是拷貝構造函數.

?

[c-sharp]?view plaincopy
  1. X::X(const?X&);??//是拷貝構造函數??????
  2. X::X(X&,?int=1);?//是拷貝構造函數?????
  3. X::X(X&,?int?a=1,?int?b=2);?//當然也是拷貝構造函數??

?

?

3. 一個類中可以存在多于一個的拷貝構造函數嗎?
解答:
類中可以存在超過一個拷貝構造函數。

[c-sharp]?view plaincopy
  1. class?X?{???
  2. public:?????????
  3. ??X(const?X&);??????//?const?的拷貝構造??
  4. ??X(X&);????????????//?非const的拷貝構造??
  5. }; ?

注意,如果一個類中只存在一個參數為 X& 的拷貝構造函數,那么就不能使用const X或volatile X的對象實行拷貝初始化.

?

[c-sharp]?view plaincopy
  1. class?X?{??????
  2. public:??
  3. ??X();??????
  4. ??X(X&);??
  5. };??????
  6. ??
  7. const?X?cx;??????
  8. X?x?=?cx;????//?error ?

如果一個類中沒有定義拷貝構造函數,那么編譯器會自動產生一個默認的拷貝構造函數。
這個默認的參數可能為?X::X(const X&)或?X::X(X&),由編譯器根據上下文決定選擇哪一個。

?

轉載于:https://www.cnblogs.com/hustcser/p/4092488.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273522.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273522.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273522.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

js中調用C標簽實現百度地圖

<script type"text/javascript"> //json數組 var jsonArray document.getElementById("restaurant").value; var map new BMap.Map("milkMap"); // 創建地圖實例 <c:forEach items"${restaurantlist}" var"…

jquery較驗組織機構編碼

//*************************組織機構碼較驗************************* function checkOrganizationCode() { var weight [3, 7, 9, 10, 5, 8, 4, 2]; var str 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ; var reg /^([0-9A-Z]){8}-[0-9|X]{1}$/; var organizationcode $("…

自定義GrildView實現單選功能

首先看實現功能截圖&#xff0c;這是一個自定義Dialog,并且里面內容由GrildView 綁定數據源&#xff0c;實現類似單選功能。 首先自定義Dialog&#xff0c;綁定數據源 自定義Dialog彈出框大小方法 最主要實現的就是點擊顏色切換的功能&#xff0c;默認GrildView的每一項都是藍色…

Java數字字符串如何轉化為數字數組

eg&#xff1a; String numberString "0123456789"; 如何轉化為&#xff1a;int[] digitArry new int[]{0,1,2,3,4,5,6,7,8,9}; 解決辦法&#xff1a; char[] digitNumberArray numberString.toCharArray(); int[] digitArry new int[digitString.toCharArray().l…

『重構--改善既有代碼的設計』讀書筆記----序

作為C的程序員&#xff0c;我從大學就開始不間斷的看書&#xff0c;看到如今上班&#xff0c;也始終堅持每天多多少少閱讀技術文章&#xff0c;書看的很多&#xff0c;但很難有一本書&#xff0c;能讓我去反復的翻閱。但唯獨『重構--改善既有代碼的設計』這本書讓我重復看了不下…

微信公共平臺接口開發--Java實現

Java微信實現&#xff0c;采用SpringMVC 架構&#xff0c;采用SAXReader解析XML RequestMapping(value"/extend") public class WeixinController { RequestMapping(value"/weixin") public ModelAndView weixin(HttpServletRequest request,HttpServlet…

最大權閉合圖hdu3996

定義&#xff1a;最大權閉合圖&#xff1a;是有向圖的一個點集&#xff0c;且該點集的所有出邊都指向該集合。即閉合圖內任意點的集合也在改閉合圖內&#xff0c;給每個點分配一個點權值Pu&#xff0c;最大權閉合圖就是使閉合圖的點權之和最大。 最小割建邊方式&#xff1a;源點…

非監督學習的單層網絡分析

這篇博客對應的是Andrew.Ng的那篇文章&#xff1a;An Analysis o f Single-Layer Networks in Unsupervised Feature Learning&#xff0c;文章的主要目的是討論receptive field size&#xff0c;number of hidden nodes&#xff0c; step-stride以及whitening在對卷積網絡模型…

Spring MVC 驗證碼

頁面 <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <% String path request.getContextPath(); String basePath request.getScheme()"://"request.getServerName()":"request.getServerP…

數據結構實驗之鏈表四:有序鏈表的歸并

數據結構實驗之鏈表四&#xff1a;有序鏈表的歸并 Time Limit: 1000MS Memory limit: 65536K 題目描述 分別輸入兩個有序的整數序列&#xff08;分別包含M和N個數據&#xff09;&#xff0c;建立兩個有序的單鏈表&#xff0c;將這兩個有序單鏈表合并成為一個大的有序單鏈表&…

apk文件編譯到系統文件中的方法(及包含so庫的)

把第三方或自己開發的apk文件編譯到系統文件(system.img)中的方法&#xff1a; 1 (1)源碼編譯后&#xff0c;把apk拷貝到out\target\product\generic\system\app中。 (2) 執行命令make snod , 把添加的spk編到system.img 中 缺點&#xff1a;執行make clean 后&#xff0c;再…

javascript中interval與setTimeOut的區別

setTimeout(code,millisec) //- 在指定時間后執行代碼 code必須&#xff1b; millisec必須&#xff1b; clearTimeout(setTimeoutId) //- 取消 setTimeout() setInterval(code,millisec)&#xff1b;//指定間隔毫秒內循環執行代碼 code必須&#xff1b; millisec必須&a…

java設計模式之單例模式(七種方法)

單例模式&#xff1a;個人認為這個是最簡單的一種設計模式&#xff0c;而且也是在我們開發中最常用的一個設計模式。 單例模式的意思就是只有一個實例。單例模式確保某一個類只有一個實例&#xff0c;而且自行實例化并向整個系統提供這個實例。這個類稱為單例類。我們前面學習的…

java 遍歷map集合

Map<String, String> map new HashMap<String, String>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); //第一種&#xff1a;通過Map.keySet遍…

poj3009 Curling 2.0 深搜

PS&#xff1a;以前看到題目這么長就沒寫下去了。今天做了半天&#xff0c;沒做出來。準備看題解&#xff0c;打開了網站都忍住了&#xff0c;最后還是靠自己做出來的。算是一點進步吧。 分析&#xff1a; 題目的意思沒明白或者理解有偏差都沒辦法做題。看樣例3和樣例4&#xf…

Android監聽事件

ListView事件監聽&#xff1a; setOnItemSelectedListener 鼠標滾動時觸發 setOnItemClickListener 點擊時觸發 EditText事件監聽&#xff1a; setOnKeyListener 獲取焦點時觸發 RadioGroup事件監聽&#xff1a; setOnCheckedChangeListener 點擊時觸發 CheckBox事件監聽&#…

子類能不能繼承父類的構造方法?

class A{ public A(){} // 1:無參數構造方法。 public A(String s){} // 2.}class B extends A{ public B(String s){ super(s); // 3. }}說明&#xff1a;如果沒有1處的無參數構造方法&#xff0c;那么3處一定要主動調用父類帶參數的構造方法。如果有1處的構造方法&#…

基于原生javascript的ajax實現

function getXMLHttpRequest(){if(window.ActiveXObject){//用戶是ie瀏覽器http_requestnew ActiveXObject("Microsoft.XMLHTTP");}else{//其他的瀏覽器http_requestnew XMLHttpRequest();}return http_request;}var httpRequest;function name(){httpRequestgetXMLH…

Google File System設計方面的問題匯總

1、Google File System概述 google file system是一個分布式文件系統&#xff0c;針對的是數據密集型應用&#xff0c;提供容錯功能&#xff0c;運行在低廉的服務器上&#xff0c;同時給大量的用戶提供高性能服務。盡管google file system有著傳統的分布式文件系統的目標&#…

linux phpize

phpize是什么 1、phpize是用來擴展php擴展模塊的&#xff0c;通過phpize可以建立php的外掛模塊。 當php編譯完后&#xff0c;在bin下面會有phpize這個腳本文件&#xff0c; 在編譯你要添加的擴展模塊之前&#xff0c;執行以下phpize就可以了&#xff1b; 比如現在想在php中加入…