1. 程式人生 > >C++建構函式詳解(包含複製建構函式)

C++建構函式詳解(包含複製建構函式)

建構函式是幹什麼的
該類物件被建立時,編譯系統物件分配記憶體空間,並自動呼叫該建構函式,由建構函式完成成員的初始化工作,故:建構函式的作用:初始化物件的資料成員。


建構函式的種類
複製程式碼
 1 class Complex 
 2 {         
 3  
 4 private :
 5     double m_real;
 6     double m_imag;
 7  
 8 public:
 9  
10     // 無引數建構函式
11     // 如果建立一個類你沒有寫任何建構函式,則系統會自動生成預設的無參建構函式,函式為空,什麼都不做
12     // 只要你寫了一個下面的某一種建構函式,系統就不會再自動生成這樣一個預設的建構函式,如果希望有一個這樣的無參建構函式,則需要自己顯示地寫出來
13     Complex(void)
14     {
15          m_real = 0.0;
16          m_imag = 0.0;
17     } 
18          
19     // 一般建構函式(也稱過載建構函式)
20     // 一般建構函式可以有各種引數形式,一個類可以有多個一般建構函式,前提是引數的個數或者型別不同(基於c++的過載函式原理)
21     // 例如:你還可以寫一個 Complex( int num)的構造函數出來
22     // 建立物件時根據傳入的引數不同調用不同的建構函式
23     Complex(double real, double imag)
24     {
25          m_real = real;
26          m_imag = imag;         
27      }
28      
29     // 複製建構函式(也稱為拷貝建構函式)
30     // 複製建構函式引數為類物件本身的引用,用於根據一個已存在的物件複製出一個新的該類的物件,一般在函式中會將已存在物件的資料成員的值複製一份到新建立的物件中
31     // 若沒有顯示的寫複製建構函式,則系統會預設建立一個複製建構函式,但當類中有指標成員時,由系統預設建立該複製建構函式會存在風險,具體原因請查詢有關 “淺拷貝” 、“深拷貝”的文章論述
32     Complex(const Complex & c)
33     {
34         // 將物件c中的資料成員值複製過來
35         m_real = c.m_real;
36         m_img  = c.m_img;
37     }            
38  
39     // 型別轉換建構函式,根據一個指定的型別的物件建立一個本類的物件
40     // 例如:下面將根據一個double型別的物件建立了一個Complex物件
41     Complex::Complex(double r)
42     {
43         m_real = r;
44         m_imag = 0.0;
45     }
46  
47     // 等號運算子過載
48     // 注意,這個類似複製建構函式,將=右邊的本類物件的值複製給等號左邊的物件,它不屬於建構函式,等號左右兩邊的物件必須已經被建立
49     // 若沒有顯示的寫=運算子過載,則系統也會建立一個預設的=運算子過載,只做一些基本的拷貝工作
50     Complex &operator=(const Complex &rhs)
51     {
52         // 首先檢測等號右邊的是否就是左邊的物件本,若是本物件本身,則直接返回
53         if ( this == &rhs ) 
54         {
55             return *this;
56         }
57              
58         // 複製等號右邊的成員到左邊的物件中
59         this->m_real = rhs.m_real;
60         this->m_imag = rhs.m_imag;
61              
62         // 把等號左邊的物件再次傳出
63         // 目的是為了支援連等 eg:    a=b=c 系統首先執行 b=c
64         // 然後執行 a= ( b=c的返回值,這裡應該是複製c值後的b物件)    
65         return *this;
66     }
67 };
複製程式碼
 下面使用上面定義的類物件來說明各個建構函式的用法:


複製程式碼
 1 void main()
 2 {
 3     // 呼叫了無參建構函式,資料成員初值被賦為0.0
 4     Complex c1,c2;
 5  
 6     // 呼叫一般建構函式,資料成員初值被賦為指定值
 7     Complex c3(1.0,2.5);
 8     // 也可以使用下面的形式
 9     Complex c3 = Complex(1.0,2.5);
10          
11     // 把c3的資料成員的值賦值給c1
12     // 由於c1已經事先被建立,故此處不會呼叫任何建構函式
13     // 只會呼叫 = 號運算子過載函式
14     c1 = c3;
15          
16     // 呼叫型別轉換建構函式
17     // 系統首先呼叫型別轉換建構函式,將5.2建立為一個本類的臨時物件,然後呼叫等號運算子過載,將該臨時物件賦值給c1
18     c2 = 5.2;
19        
20     // 呼叫拷貝建構函式( 有下面兩種呼叫方式) 
21     Complex c5(c2);
22     Complex c4 = c2;  // 注意和 = 運算子過載區分,這裡等號左邊的物件不是事先已經建立,故需要呼叫拷貝建構函式,引數為c2       
23          
24 }
複製程式碼
參考:http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html


複製建構函式
幾個原則:


C++ primer p406 :複製建構函式是一種特殊的建構函式,具有單個形參,該形參(常用const修飾)是對該類型別的引用。當定義一個新物件並用一個同類型的物件對它進行初始化時,將顯示使用複製建構函式。當該型別的物件傳遞給函式或從函式返回該型別的物件時,將隱式呼叫複製建構函式。


 


C++支援兩種初始化形式:複製初始化(int a = 5;)和直接初始化(int a(5);)對於其他型別沒有什麼區別,對於類型別直接初始化直接呼叫實參匹配的建構函式,複製初始化總是呼叫複製建構函式,也就是說:


A x(2);  //直接初始化,呼叫建構函式
A y = x;  //複製初始化,呼叫複製建構函式


 


必須定義複製建構函式的情況:


只包含類型別成員或內建型別(但不是指標型別)成員的類,無須顯式地定義複製建構函式也可以複製;有的類有一個數據成員是指標,或者是有成員表示在建構函式中分配的其他資源,這兩種情況下都必須定義複製建構函式。


 


什麼情況使用複製建構函式:


類的物件需要拷貝時,拷貝建構函式將會被呼叫。以下情況都會呼叫拷貝建構函式:
(1)一個物件以值傳遞的方式傳入函式體 
(2)一個物件以值傳遞的方式從函式返回 
(3)一個物件需要通過另外一個物件進行初始化。


 


深拷貝和淺拷貝:


所謂淺拷貝,指的是在物件複製時,只對物件中的資料成員進行簡單的賦值,預設拷貝建構函式執行的也是淺拷貝。在“深拷貝”的情況下,對於物件中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間


如果一個類擁有資源,當這個類的物件發生複製過程的時候,資源重新分配,這個過程就是深拷貝


上面提到,如果沒有自定義複製建構函式,則系統會建立預設的複製建構函式,但系統建立的預設複製建構函式只會執行“淺拷貝”,即將被拷貝物件的資料成員的值一一賦值給新建立的物件,若該類的資料成員中有指標成員,則會使得新的物件的指標所指向的地址與被拷貝物件的指標所指向的地址相同,delete該指標時則會導致兩次重複delete而出錯。下面是示例:


複製程式碼
 1 #include <iostream.h>
 2 #include <string.h>
 3 class Person 
 4 {
 5 public :
 6          
 7     // 建構函式
 8     Person(char * pN)
 9     {
10         cout << "一般建構函式被呼叫 !\n";
11         m_pName = new char[strlen(pN) + 1];
12         //在堆中開闢一個記憶體塊存放pN所指的字串
13         if(m_pName != NULL) 
14         {
15            //如果m_pName不是空指標,則把形參指標pN所指的字串複製給它
16              strcpy(m_pName ,pN);
17         }
18     }        
19        
20     // 系統建立的預設複製建構函式,只做位模式拷貝
21     Person(Person & p)    
22     { 
23         //使兩個字串指標指向同一地址位置         
24         m_pName = p.m_pName;         
25     }
26  
27     ~Person( )
28     {
29         delete m_pName;
30     }
31          
32 private :
33     char * m_pName;
34 };
35  
36 void main( )
37 { 
38     Person man("lujun");
39     Person woman(man); 
40      
41     // 結果導致   man 和    woman 的指標都指向了同一個地址
42      
43     // 函式結束析構時
44     // 同一個地址被delete兩次
45 }
46  
47  
48 // 下面自己設計複製建構函式,實現“深拷貝”,即不讓指標指向同一地址,而是重新申請一塊記憶體給新的物件的指標資料成員
49 Person(Person & chs);
50 {
51      // 用運算子new為新物件的指標資料成員分配空間
52      m_pName=new char[strlen(p.m_pName)+ 1];
53  
54      if(m_pName)         
55      {
56              // 複製內容
57             strcpy(m_pName ,chs.m_pName);
58      }
59    
60     // 則新建立的物件的m_pName與原物件chs的m_pName不再指向同一地址了
61 }
複製程式碼
 


過載賦值操作符:


通過定義operate=的函式,可以對賦值進行定義。像其他任何函式一樣,操作符函式有一個返回值和形參表。形參表必須具有與該操作符運算元書目相同的形參(如果操作符是一個成員,則包括隱式this形參)。賦值是二元運算,所以該操作符函式有兩個形參:第一個形參(隱含的this指標)對應著左運算元,第二個形參對應右運算元。


 一個應用了對賦值號過載的拷貝建構函式的例子:


複製程式碼
 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7 public:
 8     A(int);//建構函式
 9     A(const A &);//拷貝建構函式
10     ~A();
11     void print();
12     int *point;
13     A &operator=(const A &);
14 };
15 
16 A::A(int p)
17 {
18     point = new int;
19     *point = p;
20 }
21 
22 A::A(const A &b)
23 {
24     *this = b;
25     cout<<"呼叫拷貝建構函式"<<endl;
26 }
27 
28 A::~A()
29 {
30     delete point;
31 }
32 
33 void A::print()
34 {
35     cout<<"Address:"<<point<<" value:"<<*point<<endl;
36 }
37 
38 A &A::operator=(const A &b)
39 {
40     if( this != &b)
41     {
42         delete point;
43         point = new int;
44         *point = *b.point;
45     }
46 }
47 
48 
49 int main()
50 {
51     A x(2);
52     A y = x;
53     x.print();
54     delete x.point;
55     y.print();
56 
57     return 0;
58 }
複製程式碼