拷貝建構函式,類的賦值運算子多載,深拷貝與淺拷貝

來源:互聯網
上載者:User

1,首先明確:拷貝建構函式與重載賦值操作符在沒有定義的情況下,編譯器也會為我們產生一個,這說明這兩個函數是一個類必不可少的部分。如果一個類沒有定義任何的東西,編譯器也會協助我們產生下面的4個函數:1、一個建構函式,2、解構函式,3、複製建構函式,4、重載賦值操作符。

2,預設的拷貝建構函式
和 重載重載賦值操作符 都是對象間的位拷貝(淺拷貝),也就是把對象裡的值完全複製給另一個對象。在某些狀況下,類內成員變數需要動態開闢堆記憶體,如果實行位拷貝,也就是把對象裡的值完全複製給另一個對象,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這會導致2個問題:(1)A,B同時使用這塊記憶體時,會修改對方的資料,這不是程式員的本意,而且錯誤不易發現;(2)當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現運行錯誤。

3,基於2中的問題,就需要重寫拷貝建構函式
和 重載重載賦值操作符。

3.1
什麼時候需要重寫

一般,我們我們需要手動編寫解構函式的類,都需要overload 拷貝函數和賦值運算子。拷貝建構函式、重載賦值操作符、解構函式這三部分,這三個函數是一致的,如果需要手動定義了其中了一個,那麼另外的兩個也需要定義,通常在存在指標或者前期相關操作的情況下,都需要手動的定義。

3.2 例子

class A
{
public:

    A()
    {
    }
    A(int id,char *t_name)
    {
        _id=id;
        name=new char[strlen(t_name)+1];
        strcpy(name,t_name);
    }
    A(const A& a)

       {

        if(name!=NULL)
                delete name;
        _id=a._id;
        len=strlen(a.name);
        name=new char[len+1];
        strcpy(name,a.name);

        }
    A& operator =(const A& a)
//注意:此處一定要返回對象的引用,否則返回後其值立即消失!
    {
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
        name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }

    ~A()
    {
        cout<<"~destructor"<<endl;
        delete name;
    }

    int _id;
    char *name;
};

int main()
{
 A a(1,"herengang");
 A b;
 b=a;
}

3.3 解析

note1:
    不要按值向函數傳遞對象。如果對象有內部指標指向動態分配的堆記憶體,絲毫不要考慮把對象按值傳遞給函數,要按引用傳遞。並記住:若函數不能改變參數對象的狀態和目標對象的狀態,則要使用const修飾符 

note2:問題:
    對於類的成員需要動態申請堆空間的類的對象,大家都知道,我們都最好要overload其賦值函數和拷貝函數。拷貝建構函式是沒有任何傳回型別的,這點毋庸置疑。 而賦值函數可以返回多種類型,例如以上講的void,類本身class1,以及類的引用 class &? 問,這幾種賦值函數的返回各有什麼異同?
    答:1 如果賦值函數返回的是void ,我們知道,其唯一一點需要注意的是,其不支援鏈式賦值運算,即a=b=c這樣是不允許的!
          2 對於返回的是類對象本身,還是類對象的引用,其有著本質的區別!
              第一:如果其返回的是類對象本身。
   A operator =(A& a)
    {
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
       name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }
          其過程是這樣的:
                       class1 A("herengnag");
                        class1 B;   
                        B=A;
                    看似簡單的賦值操作,其所有的過程如下:
                       1 釋放對象原來的堆資源
                       2 重新申請堆空間
                       3 拷貝源的值到對象的堆空間的值
                       4 建立臨時對象(調用臨時對象拷貝建構函式),將臨時對象返回
                       5. 臨時對象結束,調用臨時對象解構函式,釋放臨時對象堆記憶體
my god,還真複雜!!
            但是,在這些步驟裡面,如果第4步,我們沒有overload 拷貝函數,也就是沒有進行深拷貝。那麼在進行第5步釋放臨時對象的heap 空間時,將釋放掉的是和目標對象同一塊的heap空間。這樣當目標對象B範圍結束調用解構函式時,就會產生錯誤!!
            因此,如果賦值運算子返回的是類對象本身,那麼一定要overload 類的拷貝函數(進行深拷貝)!
            第二:如果賦值運算子返回的是對象的引用,
   A& operator =(A& a)
    {
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
       name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }
        那麼其過程如下:
                   1 釋放掉原來對象所佔有的堆空間
                   1.申請一塊新的堆記憶體
                   2 將來源物件的堆記憶體的值copy給新的堆記憶體
                   3 返回來源物件的引用
                    4 結束。
    因此,如果賦值運算子返回的是對象引用,那麼其不會調用類的拷貝建構函式,這是問題的關鍵所在!!

4 深拷貝與淺拷貝

3中重寫的拷貝建構函式就是深拷貝。深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生複製過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.