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次調用拷貝構造函數 }