1. 程式人生 > >複製建構函式,賦值操作符過載之間的區別

複製建構函式,賦值操作符過載之間的區別

  1. 何時呼叫拷貝(複製)建構函式:

    StringBad ditto (motto);   
    StringBad metoo = motto; 
    StringBad also = StringBad(motto); 
    StringBad * pStringBad = new StringBad (motto);

    以上4中方式都將呼叫:StringBad(const StringBad &)

    • 其中中間兩種宣告可能會使用複製建構函式直接建立metoo和also物件,也可能使用複製建構函式生成一個臨時物件,然後將臨時物件的內容賦給metoo和also,這取決於具體的實現。最後一種宣告使用motto初始化一個匿名物件,並將新物件的地址賦給pStringBad指標。
    • 每當程式生成了物件副本時,編譯器都將使用複製建構函式。具體的說,當函式按值傳遞物件或函式返回物件時,都將使用複製建構函式。記住,按值傳遞意味著建立原始變數的一個副本。
    • 編譯器生成臨時物件時,也將使用複製建構函式。例如,將3個Vector物件相加時,編譯器可能生成臨時的Vector物件來儲存中間的結果。
    • 另外,String sailor = sports;等價於String sailor = (String)sports;因此呼叫的是拷貝建構函式
  2. 何時呼叫賦值運算子:

    • 已有的物件賦給另一個物件時,將呼叫過載的賦值運算子。
    • 初始化物件時,並不一定會使用賦值操作符:
      StringBad metoo=knot;   // use copy constructor, possibly assignment, too
      這裡,metoo是一個新建立的物件,被初始化為knot的值,因此使用賦值建構函式。不過,正如前面指出的,實現時也可能分兩步來處理這條語句:使用複製建構函式建立一個臨時物件,然後通過賦值操作符將臨時物件的值複製到新物件中。這就是說,初始化總是會呼叫複製建構函式,而使用=操作符時也可能呼叫賦值建構函式。

    與複製建構函式相似,賦值運算子的隱式實現也對成員進行逐個複製。如果成員本身就是類物件,則程式將使用為這個類定義的賦值運算子來複制該成員,但靜態資料成員不受影響。

  3. 賦值運算子和拷貝建構函式在實現上的區別:

    • 由於目標物件可能引用了以前分配的資料,所以函式應使用delete[]來釋放這些資料。
    • 函式應當避免將物件賦給自身;否則給物件重新賦值前,釋放記憶體操作可能刪除物件的內容。
    • 函式返回一個指向呼叫物件的引用(方便串聯使用),而拷貝建構函式沒有返回值。

    下面的程式碼說明了如何為StringBad類編寫賦值操作符:

    StringBad & StringBad::operator=(const StringBad & st)
    {
     if(this == & st)
        return * this;
     delete [] str;
     len = st.len;
     str = new char [len + 1];
     strcpy(str,st.str);
     return *this;
    }

    程式碼首先檢查自我複製,這是通過檢視賦值操作符右邊的地址(&s)是否與接收物件(this)的地址相同來完成的,如果相同,程式將返回*this,然後結束。
    如果不同,釋放str指向的記憶體,這是因為稍後將把一個新字串的地址賦給str。如果不首先使用delete操作符,則上述字串將保留在記憶體中。由於程式程式不再包含指向字串的指標,一次這些記憶體被浪費掉。
    接下來的操作與複製建構函式相似,即為新字串分配足夠的記憶體空間,然後複製字串。
    賦值操作並不建立新的物件,因此不需要調整靜態資料成員num_strings的值。

  4. class TestChild
    {
    public:
        TestChild()
        {
            x=0;
            y=0;
            printf("TestChild: Constructor be called!\n");
        }
        ~TestChild(){}
        TestChild(const TestChild& tc)
        {
            x=tc.x;
            y=tc.y;
            printf("TestChild: Copy Constructor called!//因為寫在了Test(拷貝)建構函式的初始化列表裡\n");
        }
        
        const TestChild& operator=(const TestChild& right)
        {
            x=right.x;
            y=right.y;
            printf("TestChild: Operator = be called! //因為寫在了Test(拷貝)建構函式的函式體裡\n");
            return *this;
        }
    
        int x,y;
    };
    
    class Test
    {
    public:
    
        Test(){printf("Test:      Constructor be called!\n");}
        explicit Test(const TestChild& tcc)
        {
            tc=tcc;
        }
        ~Test(){}
        Test(const Test& test):tc(test.tc)
        {
            tc=test.tc;
            printf("Test:      Copy Constructor be called!\n");
        }
    
        const Test & operator=(const Test& right)
        {
            tc=right.tc;
            printf("Test:      Operator= be called!\n");
            return *this;
        }
    
        TestChild tc;
    };
    
    int main()
    {
        printf("1、Test中包含一個TestChild,這兩個類分別具有建構函式、\n   拷貝建構函式、過載operator=。\n\n");
        printf("2、在呼叫Test的建構函式和拷貝建構函式之前,會根據跟在\n   這些函式後的初始化列表去初始化其\n   TestChild變數(呼叫TestChild的拷貝建構函式去初始化)\n\n");
        printf("3、一旦進入Test的建構函式體或拷貝建構函式體,則說明其成員變數TestChild已\n   經通過TestChild的建構函式或TestChild的拷貝建構函式構造出了物件\n");
        printf("   所以,在Test的建構函式體或拷貝建構函式體中,再去使用=號\n   給TestChild的時候,呼叫的就是TestChild的operator=,\n   而不是TestChild的拷貝構造函數了\n");
        printf("   這就是Test建構函式後面 “:” 初始化列表的存在意義!(\n   為了呼叫成員變數的建構函式或者拷貝建構函式)\n\n");
        printf("4、最後!揪出讓人困惑的終極原因!!!!!\n   Test test2=test1和Test test2(test1)這兩種是TM一模一樣的\n   (都呼叫拷貝建構函式)!!!!除了這點兒之外,其他地方都是該是什麼是什麼(\"()\"呼叫建構函式,\"=\"呼叫賦值操作符)!!!\n\n");
        printf("5、一個物件初始化完畢後,所有對這個物件的賦值都呼叫operator=\n\n輸出如下:");
    
        printf("Test test1; DO:\n");
        Test test1;
        printf("\n");
        printf("Test test2=test1; DO:\n");
        Test test2=test1;
        printf("\n");
        printf("Test test3(test2); DO:\n");
        Test test3(test2);
        printf("\n");
        printf("test3=test1; DO:\n");
        test3=test1;
    
         return 0;
    }