[C++]拷貝構造函數

一.拷貝構造函數

      拷貝構造函數也是類的一個重載版本的構造函數,它可以用一個已知的對象初始化一個被建立的同類新對象。該函數的形式參數是本類對象的常引用,所以與普通構造函數在形式參數上有很是明顯的區別。跟構造函數同樣,C++爲每個類定義了一個默認的拷貝構造函數,能夠實現將實參對象的數據成員值複製到新建立的當前對象對應的數據成員中。用戶能夠根據須要定義本身的拷貝構造函數,從而實現同類對象之間數據成員的值傳遞。ios

      拷貝構造函數的定義格式以下:函數

class 類名
{
public:spa

      類名(const 類名&對象名);
      ...
};3d

拷貝構造函數是一種特殊的構造函數,在建立類的對象時若是實際參數是本類的對象,則調用拷貝構造函數。指針

如下3種狀況系統自動調用拷貝構造函數:code

  1. 明確表示由一個對象初始化另外一個對象時;
  2. 當對象做爲函數的實際參數傳遞給函數的值形式參數時。注意,若是形式參數時引用參數或指針參數,都不會調用拷貝構造函數,由於此時不會產生新對象;
  3. 當對象做爲函數返回值時。

      給一個程序幫助理解:對象

 1 #include"stdafx.h"
 2 #include<iostream>
 3 using namespace std;
 4 
 5 class TDate
 6 {
 7 private:
 8     int year,month,day;
 9 public:
10     TDate(int y=2013,int m=9,int d=28);
11     TDate(const TDate &date);
12     ~TDate()
13     {
14         cout<<"Deconstructor called."<<endl;
15     }
16     void Print();
17 };
18 
19 TDate::TDate(int y,int m,int d)
20 {
21     year=y;
22     month=m;
23     day=d;
24     cout<<"Constructor called."<<endl;
25 }
26 
27 TDate::TDate(const TDate &date)
28 {
29     year=date.year;
30     month=date.month;
31     day=date.day;
32     cout<<"Copy Constructor called."<<endl;
33 }
34 
35 void TDate::Print()
36 {
37     cout<<year<<"."<<month<<"."<<day<<endl;
38 }
39 
40 TDate f(TDate Q)
41 {
42     TDate R(Q);
43     return Q;
44 }
45 
46 void main()
47 {
48     TDate day1(2013,1,1);//調用普通帶參構造函數
49     TDate day3;//調用普通構造函數,使用默認參數值
50     TDate day2(day1);//調用拷貝構造函數
51     TDate day4=day2;//調用拷貝構造函數
52     day3=day2;//複製語句,不調用任何構造函數
53     day3=f(day2);//實參傳值給形參Q調用拷貝構造函數
54                  //f內部定義對象R(Q)時調用拷貝構造函數
55                  //返回Q調用拷貝構造函數
56     day3.Print();

       前3個析構函數是f函數調用結束時引發的,後4個析構函數是day1-day4生命期結束調用的,他們的調用順序與構造函數徹底相反。blog

二.深拷貝與淺拷貝 

      系統爲每個類提供的默認拷貝構造函數,能夠實現將源對象全部數據成員的值逐一賦值給目標對象相應的數據成員。若是講上面程序中拷貝構造函數的原型聲明及定義去掉,並不影響程序的正確執行,結果以下:內存

      能夠看到,析構函數調用了7次,說明有5次調用的是析構函數。那麼何時必須爲類定義拷貝構造函數呢?字符串

      一般,若是一個類包含指向動態存儲空間指針類型的數據成員,而且經過該指針在構造函數中動態申請了空間,則必須爲該類定義一個拷貝構造函數不然在析構時容易出現意外錯誤。

 1 #include"stdafx.h"
 2 #include<iostream>
 3 using namespace std;
 4 
 5 class String
 6 {
 7 private:
 8     char *S;
 9 public:
10     String(char *p=0);
11 //    String(const String &s1);
12     ~String();
13     void Show();
14 };
15 
16 String::String(char *p)
17 {
18     if(p)
19     {
20         S=new char[strlen(p)+1];
21         strcpy(S,p);
22     }
23     else S=0;
24 }
25 /*
26 String::String(const String &s1)
27 {
28     if(s1.S)
29     {
30         S=new char[strlen(s1.S)+1];
31         strcpy(S,s1.S);
32     }
33     else S=0;
34 }
35 */
36 String::~String()
37 {
38     if(S) delete[]S;
39 }
40 
41 void String::Show()
42 {
43     cout<<"S="<<S<<endl;
44 }
45 
46 void main()
47 {
48     String s1("teacher");
49     String s2(s1);
50     s1.Show();
51     s2.Show();
52 }

      該程序在編譯無error也無warning,但在執行後會報錯,中斷執行。由於在執行String s1("teacher");語句時,構造函數動態地分配存儲空間,並將返回的地址賦給對象s1的成員S,而後把teacher的內容拷貝到這塊空間。

      因爲String沒有定義拷貝構造函數,所以當語句Sttring s2(s1);定義對象s2時,系統將調用默認的拷貝構造函數,負責將對象s1的數據成員S中存放的地址值賦值給對象s2的數據成員S。

      上圖中,對象s1複製給對象s2的僅是其數據成員S的值,並無把S所指向的動態存儲空間進行復制,這種複製稱爲淺拷貝

      淺拷貝的反作用是在調用s1.Show();與s2.show();時看不出有什麼問題,由於兩個對象的成員S所指向的存儲區域是相同的,都能正確訪問。可是,當遇到對象的生命期結束須要撤銷對象時,首先由s2對象調用析構函數,將S成員所指向的字符串teacher所在的動態空間釋放,其數據成員S成爲懸掛指針。那麼在s1自動調用析構函數的時候,沒法正確執行析構函數代碼delete[]S,從而致使出錯。

      咱們經過定義拷貝函數實現深拷貝能夠解決淺拷貝所帶來的指針懸掛問題。深拷貝指不復制指針值自己,而是複製指針所指向的動態空間中的內容。這樣,兩個對象的指針成員就擁有不一樣的地址值,指向不一樣的動態存儲空間首地址,而兩個動態空間的內容徹底同樣。

      在上面的程序中添加被註釋的語句,在執行String s2(s1);時,使用對象s1去建立對象s2,調用自定義的拷貝構造函數,當前新對象經過數據成員S另外申請了一快內存,而後將已知對象s1的數據成員S複製到當前對象s2的S所指向的內存空間。

       此時運行程序在析構時不存在指針懸掛的問題,程序能夠正確運行。

相關文章
相關標籤/搜索