1. 程式人生 > >c++學習第六篇(拷貝建構函式)

c++學習第六篇(拷貝建構函式)

1.當函式的引數為類的物件時

#include<iostream>
using namespace std;
class CExample
{
private:
    int a;
public:
    CExample(int b)
    {
        a=b;
        printf("constructor is called\n");
    }
    CExample(const CExample & c)
    {
        a=c.a;
        printf("copy constructor is called\n");
    }
    ~CExample()
    {
     cout<<"destructor is called\n";
    }
    void Show()
    {
     cout<<a<<endl;
    }
};
void g_fun(CExample c)
{
    cout<<"g_func"<<endl;
}
int main()
{
    CExample A(100);
    CExample B=A;
    B.Show(); 
    g_fun(A);
    return 0;
}

 呼叫g_fun()時,會產生以下幾個重要步驟:
(1).A物件傳入形參時,會先會產生一個臨時變數,就叫 C 吧。
(2).然後呼叫拷貝建構函式把A的值給C。 整個這兩個步驟有點像:CExample C(A);
(3).等g_fun()執行完後, 析構掉 C 物件。  

    

2. 函式的返回值是類的物件

#include<iostream>
using namespace std;
class CExample
{
private:
    int a;
public:
    //建構函式
    CExample(int b)
    {
     a=b;
        printf("constructor is called\n");
    }
    //拷貝建構函式
    CExample(const CExample & c)
    {
     a=c.a;
        printf("copy constructor is called\n");
    }
    //解構函式
    ~CExample()
    {
     cout<<"destructor is called\n";
    }
    void Show()
    {
     cout<<a<<endl;
    }
};
CExample g_fun()
{
    CExample temp(0);
    return temp;
}
int main()
{
    
    g_fun();
    return 0;
}

當g_Fun()函式執行到return時,會產生以下幾個重要步驟:
(1). 先會產生一個臨時變數,就叫XXXX吧。
(2). 然後呼叫拷貝建構函式把temp的值給XXXX。整個這兩個步驟有點像:CExample XXXX(temp);
(3). 在函式執行到最後先析構temp區域性變數。
(4). 等g_fun()執行完後再析構掉XXXX物件。  
  

3. 物件需要通過另外一個物件進行初始化

CExample A(100);
CExample B=A;

三、淺拷貝與深拷貝

淺拷貝

    所謂淺拷貝,指的是在物件複製時,只對物件中的資料成員進行簡單的賦值,預設拷貝建構函式執行的也是淺拷貝。大多情況下“淺拷貝”已經能很好地工作了,但是一旦物件存在了動態成員,那麼淺拷貝就會出問題了,讓我們考慮如下一段程式碼:

#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
    Rect()
    {
     p=new int(100);
    }
   
    ~Rect()
    {
     assert(p!=NULL);
        delete p;
    }
private:
    int width;
    int height;
    int *p;
};
int main()
{
    Rect rect1;
    Rect rect2(rect1);
    return 0;
}

在這段程式碼執行結束之前,會出現一個執行錯誤。原因就在於在進行物件複製時,對於動態分配的內容沒有進行正確的操作。我們來分析一下:

    在執行定義rect1物件後,由於在建構函式中有一個動態分配的語句,因此執行後的記憶體情況大致如下:

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

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

 3. 深拷貝

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

#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
    Rect()
    {
     p=new int(100);
    }
    
    Rect(const Rect& r)
    {
     width=r.width;
        height=r.height;
     p=new int(100);
        *p=*(r.p);
    }
     
    ~Rect()
    {
     assert(p!=NULL);
        delete p;
    }
private:
    int width;
    int height;
    int *p;
};
int main()
{
    Rect rect1;
    Rect rect2(rect1);
    return 0;
}

此時,在完成物件的複製後,記憶體的一個大致情況如下:
   
此時rect1的p和rect2的p各自指向一段記憶體空間,但它們指向的空間具有相同的內容,這就是所謂的“深拷貝”。

 

3. 防止預設拷貝發生

    通過對物件複製的分析,我們發現物件的複製大多在進行“值傳遞”時發生,這裡有一個小技巧可以防止按值傳遞——宣告一個私有拷貝建構函式。甚至不必去定義這個拷貝建構函式,這樣因為拷貝建構函式是私有的,如果使用者試圖按值傳遞或函式返回該類物件,將得到一個編譯錯誤,從而可以避免按值傳遞或返回物件。

//防止按值傳遞
class CExample 

private: 
    int a; 
  
public: 
    //建構函式
    CExample(int b) 
    { 
        a = b; 
        cout<<"creat: "<<a<<endl; 
    } 
  
private: 
    //拷貝建構函式,只是宣告
    CExample(const CExample& C); 
  
public: 
    ~CExample() 
    { 
        cout<< "delete: "<<a<<endl; 
    } 
  
    void Show () 
    { 
        cout<<a<<endl; 
    } 
}; 
  
//???? 
void g_Fun(CExample C) 

    cout<<"test"<<endl; 

  
int main() 

    CExample test(1); 
    //g_Fun(test);   //按值傳遞將出錯
      
    return 0; 
}

小結:
    拷貝有兩種:深拷貝,淺拷貝。

      當出現類的等號賦值時,會呼叫拷貝函式,在未定義顯示拷貝建構函式的情況下,系統會呼叫預設的拷貝函式——即淺拷貝,它能夠完成成員的一一複製。當資料成員中沒有指標時,淺拷貝是可行的。但當資料成員中有指標時,如果採用簡單的淺拷貝,則兩類中的兩個指標將指向同一個地址,當物件快結束時,會呼叫兩次解構函式,而導致指標懸掛現象。所以,這時,必須採用深拷貝。

     深拷貝與淺拷貝的區別就在於深拷貝會在堆記憶體中另外申請空間來儲存資料,從而也就解決了指標懸掛的問題。簡而言之,當資料成員中有指標時,必須要用深拷貝