C++:對象的賦值和復制


3.6.1 對象賦值語句

如同基本類型賦值語句一樣,同類型的對象之間也可以進行賦值,即一個對象的值可以賦給
另一個對象。這里所指的對象的賦值是指對其中的數據成員賦值,而不對成員函數賦值。
例如:A和B是同一類的兩個對象,那么下述對象賦值語句
B=A;
就能把對象A的數據成員的值逐位復制給對象B

//例3.24 對象賦值語句示例
#include<iostream>
using namespace std;
class Myclass{
 public:
       void set(int i,int j)
       {
        a = i;
        b = j;
       }
       void show()
       {
        cout<<a<<" "<<b<<endl;
       }
 private:
       int a,b;       
}; 
int main()
{
 Myclass o1,o2;
 o1.set(20,5);
 o2 = o1;          //將對象o1的值賦給對象o2 
 o1.show();
 o2.show();
 return 0;
}
/*
 該程序中,語句:
   o2 = o1;
 等價於語句:
   o2.a = o1.a;
   o2.b = o1.b;   
 因此,運行此程序將顯示:
   20 5
   20 5
  說明:
  1、在使用對象賦值語句進行對象賦值時,兩個對象的類型必須相同,如對象的類型不同,
     編譯時將出錯。
  2、兩個對象之間的賦值,僅僅使這些對象中數據成員相同,而兩個對象仍是分離的。例如
     本例對象后,再調用o1.set()設置o1的值,不會影響o2的值。
  3、對象賦值是通過默認賦值運算符函數實現的
  4、將一個對象的值賦給另一個對象時,多數情況下都是成功的,但當類中存在指針時,可能
     會產生錯誤。              
*/

3.6.2 拷貝構造函數

拷貝構造函數是一種特殊的構造函數,其形參是本類對象的引用。拷貝構造函數的作用
是,在建立一個新對象時,使用一個已經存在的對象去初始化這個新對象。
例如: Point p2(p1);
其作用是:在建立新對象p2時,用已經存在的對象p1去初始化對象p2,在這個過程中就要
調用拷貝構造函數。

拷貝構造函數具有以下特點:
(1)因為該函數也是一種構造函數,所以其函數名與類名相同,並且該函數也沒有返回值
類型。
(2)該函數只有一個參數,並且是同類對象的引用
(3)每一個類都必須有一個拷貝構造函數。程序員可以自定義拷貝構造函數,用於按照需要
初始化新對象。如果程序員沒有定義類的拷貝構造函數,系統就會自動生成產生一個默認的拷貝
構造函數,用於復制出數據成員值完全相同的新對象。

1. 自定義拷貝構造函數

一般格式: 類名::類名(const 類名 &對象名)
{
       //拷貝構造函數的函數體
}

下面是一個用戶自定義的拷貝構造函數:
class Point{
               public:
             Point(int a,int b) //構造函數
                    {
                         x = a;
                         y = b;
         }
                   Point(const Point &p) //拷貝構造函數
                   {
                        x = 2*p.x;
                        y = 2*p.y;
                    }
               .....
              private:
                       int x;
                       int y;
};
假如p1為類Point的一個對象,則下述語句可以在建立新對象p2時調用拷貝構造函數初始化p2;
Point p2(p1);

//例3.25 自定義拷貝構造函數的使用。

#include<iostream>
using namespace std;
class Point{
       public:
             Point(int a,int b)  //普通構造函數 
             {
               x = a;
               y = b;
             }
             Point::Point(const Point &p) //自定義的拷貝構造函數 
             {
               x = 2*p.x;
               y = 2*p.y;
             }
             void print()
             {
              cout<<x<<" "<<y<<endl; 
             }
       private:
               int x;
               int y;         
};
int main()
{
 Point p1(30,40);       //定義對象p1,調用了普通的構造函數
 Point p2(p1);          //調用拷貝構造函數,用對象p1初始化對象p2 
 p1.print();
 p2.print();
 return 0; 
} 
/*  本例中定義對象p2時,調用了自定義拷貝構造函數。程序運行結果如下:
        30 40 
        60 80
    
    調用拷貝構造函數的一般形式為:
    類名 對象2(對象1);
    
    上面的這種拷貝構造函數的方法稱為“代入法”。除了用代入法調用拷貝構造函數外,
還可以采用"賦值法"調用拷貝構造函數,與基本類型的變量初始化類似.這種調用方的一般格式為:
    類名 對象2=對象1;
    當然,這種方法可以在一個語句中進行多個對象的復制。如
    Point p2=p1,p3=p1; 
    
如將例3.25主函數main改為如下形式:
int main()
{
 Point p1(10,20);
 Point p2=p1;  //以賦值法調用拷貝構造函數 
 p1.print();
 p2.print();
 return 0; 
}     

2. 默認的拷貝構造函數(程序員沒有定義,系統會自動生成)

// 例3.26 默認的拷貝構造函數

#include<iostream>
using namespace std;
class Point{
 public:
        Point(int a,int b) //普通的構造函數 
        {
         x = a;
         y = b;
        }
        
//        Point::Point(const Point &p)//不用寫,系統會默認存在(需要用時直接調用) 
//        {
//         x = p.x;
//         y = p.y;
//        }
    
        void print()
        {
         cout<<x<<" "<<y<<endl;
        } 
 private:
        int x;
        int y;
};
int main()
{
        Point p1(30,40);  //調用普通構造函數 
        Point p2(p1);     //用代入法調用默認的拷貝構造函數,用對象p1初始化對象p2 
        Point p3=p1;      //用賦值法調用默認的拷貝構造函數,用對象p1初始化對象p3
        p1.print();
        p2.print();
        p3.print();
        return 0;          
} 

/*
   調用拷貝構造函數的方法有兩種:代入法、賦值法
   
   代入法:Point p2(p1)

   賦值法:Point p2=p1 

*/

3.調用拷貝構造函數的三種情況

(1)當用類的一個對象去初始化另一個對象時,拷貝構造函數將會被調用,
如例3.26主函數中的下屬語句
Point p2(p1); //用代入法調用默認的拷貝構造函數,用對象p1初始化對象p2
Point p3=p1; //用賦值法調用默認的拷貝構造函數,用對象p1初始化對象p3
(2)當函數的形參是類的對象,在調用函數進行形參和實參結合時,拷貝構造函數將會被調用
例如:

void fun1(Point p) //形參是類Point的對象p
{
   p.print();
}
int main()
{
   point p1(10,20);
   fun1(p1); //調用函數fun1時,實參p1是類Point的對象
   //將調用拷貝構造函數,初始化形參對象p
   return 0;
}

理解:在main函數內,執行語句“fun1(p1)”,便是這種情況。在調用這個函數時,對象p1是實參
用它來初始化被調用函數的形參p時,需要調用拷貝構造函數。這時,如果類Point中有自定義
的拷貝構造函數時,就調用拷貝的構造函數,否則就調用系統自動生成的默認拷貝構造函數

(3)當函數的 返回值是類的對象,在函數調用完畢將返回值(對象)帶回調用處時。此時將會
調用拷貝構造函數,將此對象賦值給一個臨時對象並傳到該函數的調用處。

例如: Point fun2() //函數fun2()的返回值類型是Point類類型
{
    Point p1(10,30); //定義類Point的對象p1
    return p1; // 函數的返回值是Point類的對象
}
int main()
{
   Point p2; //定義類Point的對象p1
   p2=fun2(); //函數執行完成,返回調用者時,調用拷貝構造函數(賦值法)
   return 0;
}

理解:
由於對象p1是函數fun2中定義的,在調用函數fun2結束時,p1的生命周期結束了,因此在
函數fun2結束之前,執行語句"return p1"時,將會調用拷貝構造函數將p1的值復制到一個
臨時對象中,這個臨時對象是系統在主程序中臨時創建的。函數運行結束時,p1對象消失,
但是臨時對象將會通過語句"p2=fun2()"將它的值賦給對象p2,執行完這個語句后,臨時對
象變自動消失了。

//例3.27  演示調用拷貝構造函數的3中情況

#include<iostream>
using namespace std;
class Point{
  public:
          Point(int a=0,int b=0); //聲明構造函數
          Point::Point(const Point &p); //聲明拷貝構造函數
        void print()
        {
          cout<<x<<" "<<y<<endl; 
        } 
  private:
        int x,y;
}; 
Point::Point(int a,int b)  //定義構造函數 
{
   x = a;
   y = b;
   cout<<"Using normal constructor\n";
}
Point::Point(const Point &p)//定義拷貝構造函數 
{
   x = 2 * p.x;
   y = 2 * p.y;
   cout<<"Using copy constructor\n"; 
} 
void fun1(Point p)    //形參是類Point的對象p
{
   p.print(); 
}
Point fun2()  //函數fun2()的返回值類型是Point類類型 
{
   Point p4(10,30);  //定義類Point的對象p4 
   return p4;        // 函數的返回值是Point類的對象 
} 
int main()
{
   Point p1(30,40);//定義對象p1時,第1次調用普通的構造函數 
   p1.print();      
   
   Point p2(p1); //用帶入法,用對象p1為對象p2進行初始化。此時會第1次調用拷貝構造函數 
   p2.print();
   
   Point p3=p1; //用賦值法,用對象p1為對象p3進行初始化。此時會第2次調用拷貝構造函數 
   p3.print();
   
   fun1(p1);  //在調用fun1函數,實參和形參結合時, 此時會第3次調用拷貝構造函數 
   
   p2=fun2(); //在調用fun2函數,在函數內部第2次調用普通構造函數。而且,當調用fun2函數結束時,
              //還是用賦值法,用返回的對象p4為對象p2進行賦值,會第4次調用拷貝構造函數 
   p2.print();
   
   return 0;  
} 

運行結果:
          Using normal constructor 
          30    40 
          Using Cpy constructor 
          60    80 
          Using Cpy constructor 
          60    80 
          Using Cpy constructor 
          60    80 
          Using normal constructor
          Using Cpy constructor
          20    60
          
          
當沒有自定義的拷貝構造函數時的運行結果:
        Using normal constructor 
          30    40 
          30    40 
          30    40  
          30    40 
          Using normal constructor
          10    30         

//再舉一個實例為:

#include<iostream>
using namespace std;
class Point{
  public:
    Point(int a,int b)
     {
       x = a;
       y = b;
     }
     /* Point::*/Point(const Point &p) //拷貝構造函數(用初始化過的對象為沒有初始化過的對象進行初始化) 
     {
       x = 2 * p.x;
       y = 2 * p.y;
     }
     Point& operator = (const Point &p)//賦值運算符重載函數(用初始化過的對象為初始化過的對象進行賦值) 
     {
       x = p.x;
       y = p.y;
     }
     Point fun();
     void print()
     {
       cout<<x<<" "<<y<<endl;
     }
   private:
        int x,y;     
};
Point::Point fun(Point p4)
{
    return p4; 
}
int main()
{
  Point p1(10,20);
  Point p3(15,15);
  
  p3=p1;//調用賦值運算符重載函數 
  p3.print();
  
  Point p2 = fun(p1);//調用fun函數時,實參和形參結合,會第1次調用拷貝構造函數;調用fun函數結束時,
   p2.print() ;         // 返回的對象p4為未初始化的p2進行初始化,會第2次調用拷貝構造函數 
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM