| 默認拷貝構造函數的行為如下: 默認的拷貝構造函數執行的順序與其他用戶(hù)定義的構造函數相同,執行先父類(lèi)后子類(lèi)的構造. 拷貝構造函數對類(lèi)中每一個(gè)數據成員執行成員拷貝(memberwise Copy)的動(dòng)作. a)如果數據成員為某一個(gè)類(lèi)的實(shí)例,那么調用此類(lèi)的拷貝構造函數. b)如果數據成員是一個(gè)數組,對數組的每一個(gè)執行按位拷貝. c)如果數據成員是一個(gè)數量,如int,double,那么調用系統內建的賦值運算符對其進(jìn)行賦值.
1.深拷與淺拷 深拷貝和淺拷貝的定義可以簡(jiǎn)單理解成:如果一個(gè)類(lèi)擁有資源(堆,或者是其它系統資源),當這個(gè)類(lèi)的對象發(fā)生復制過(guò)程的時(shí)候(復制指針所指向的值),這個(gè)過(guò)程就可以叫做深拷貝,反之對象存在資源但復制過(guò)程并未復制資源(只復制了指針所指的地址)的情況視為淺拷貝。 淺拷貝資源后在釋放資源的時(shí)候會(huì )產(chǎn)生資源歸屬不清的情況導致程序運行出錯,這點(diǎn)尤其需要注意! 原則上,應該為所有包含動(dòng)態(tài)分配成員的類(lèi)都提供拷貝構造函數。 淺: using namespace std;
//shallow && deep copy //deep copy make pointer point to a new place! class Product ...{ public:int* pointer; Product(int i=0) ...{ pointer=new int(i); } //change class variable void change(int i) ...{ *pointer=i; }
//deconstructor ~Product() ...{ delete pointer; } }; int main() ...{ Product p1(2); Product p2(p1); p1.change(3); cout<<*p2.pointer<<endl;
getchar(); return 0; } 深: using namespace std;
//shallow && deep copy //deep copy make pointer point to a new place! class Product ...{ public:int* pointer; Product(int i=0) ...{ pointer=new int(i); } //change class variable void change(int i) ...{ *pointer=i; }
// copying constructor Product(const Product &p) ...{ pointer=new int(*p.pointer); } //deconstructor ~Product() ...{ delete pointer; } }; int main() ...{ Product p1(2); Product p2(p1); p1.change(3); cout<<*p2.pointer<<endl;
getchar(); return 0; } 2 拷貝構造函數的另一種調用 當對象直接作為參數傳給函數時(shí),函數將建立對象的臨時(shí)拷貝,這個(gè)拷貝過(guò)程也將調用拷貝構造函數。 例如:
#include <iostream> using namespace std;
class Date...{ int n; public: Date(int i = 0) ...{ cout << "載入構造函數" << endl; n = i; } Date(const Date &d) ...{ cout << "載入拷貝構造函數" << endl; n = d.n; } int GetMember() ...{ return n; } };
void Display(Date obj) //針對obj的操作實(shí)際上是針對復制后的臨時(shí)拷貝進(jìn)行的 ...{ cout << obj.GetMember() << endl; }
int main() ...{ Date a; Date b(99); Display(a); //對象直接作為參數 Display(b); //對象直接作為參數 getchar(); return 0; }
程序輸出: 載入構造函數: 載入構造函數: 載入拷貝構造函數 0載入拷貝構造函數 99 還有一種情況,也是與臨時(shí)對象有關(guān)的。 當函數中的局部對象被用作返回值,返回給函數調用時(shí),也將建立此局部對象的一個(gè)臨時(shí)拷貝,此時(shí)拷貝構造函數也將被調用?!墒墙?jīng)測試發(fā)現情況有異。 代碼如下:
#include <iostream> using namespace std;
class Date...{ int n; public: Date(int i = 0) ...{ cout << "載入構造函數" << endl; n = i; } Date(const Date &d) ...{ cout << "載入拷貝構造函數" << endl; n = d.n; } void Show() ...{ cout << "n = " << n << endl; } };
Date GetClass(void) //函數中的局部對象被用作返回值,按理說(shuō)應該引用拷貝構造函數 ...{ Date temp(100); return temp; }
int main() ...{ Date a; a.Show(); a = GetClass();//這里GetClass()函數中的局部對象被用作返回值 a.Show(); Date b = GetClass();//這里GetClass()函數中的局部對象被用作返回值 b.Show(); getchar(); return 0; }
程序輸出: 載入構造函數: n = 0 載入構造函數: n = 100 載入構造函數: n = 100 按理第2個(gè)和第3個(gè)應該輸出'載入拷貝構造函數"才對,這個(gè)結果與預想的不一樣,到底是哪里出問(wèn)題了呢? 注:后來(lái)有論壇上的朋友告訴我說(shuō)這是因為編譯器的不同而導致不同的輸出。 有人得到這樣的輸出結果: 載入構造函數 n = 0 載入構造函數 載入拷貝構造函數 n = 100 載入構造函數 載入拷貝構造函數 n = 100 還有人得到這樣的輸出結果: 載入構造函數 n = 0 載入構造函數 載入拷貝構造函數 n = 100 載入構造函數 載入拷貝構造函數 載入拷貝構造函數 n = 100 (用的是vc++) 3.3 無(wú)名對象 現在我們來(lái)說(shuō)一下無(wú)名對象。什么是無(wú)名對象?利用無(wú)名對象初始化對象系統不會(huì )調用拷貝構造函數?這是我們需要回答的兩個(gè)問(wèn)題。 首先我們來(lái)回答第一個(gè)問(wèn)題。很簡(jiǎn)單,如果在程序的main函數中有: Internet ("中國"); //Internet表示一個(gè)類(lèi) 這樣的一句語(yǔ)句就會(huì )產(chǎn)生一個(gè)無(wú)名對象。 無(wú)名對象會(huì )調用構造函數,但利用無(wú)名對象初始化對象時(shí)系統不會(huì )調用拷貝構造函數! 下面的代碼是常見(jiàn)的利用無(wú)名對象初始化對象的例子。
#include <iostream> using namespace std;
class Date...{ int n; public: Date(int i = 0) ...{ cout << "載入構造函數" << endl; n = i; } Date(const Date &d) ...{ cout << "載入拷貝構造函數" << endl; n = d.n; } void Show() ...{ cout << "n = " << n << endl; } };
int main() ...{ Date a(100); a.Show(); Date b = a; //"="在對象聲明語(yǔ)句中,表示初始化,調用拷貝構造函數 b.Show(); Date c; c.Show(); c = a; //"="在賦值語(yǔ)句中,表示賦值操作,調用賦值函數 c.Show(); getchar(); return 0; }
程序輸出: 載入構造函數: name的地址: 23ff40;name的字符串: 中國 cname的地址: 33778;cname的字符串: 中國 載入析構函數: 上面代碼的運行結果有點(diǎn)“出人意料”,從思維邏輯上說(shuō),當無(wú)名對象創(chuàng )建了后,是應該調用自定義拷貝構造函數,或者是默認拷貝構造函數來(lái)完成復制過(guò)程的,但事實(shí)上系統并沒(méi)有這么做,因為無(wú)名對象使用過(guò)后在整個(gè)程序中就失去了作用。對于這種情況c++會(huì )把代碼看成是: Internet a ("中國"); 省略了創(chuàng )建無(wú)名對象這一過(guò)程,所以說(shuō)不會(huì )調用拷貝構造函數。 3.賦值符的重載 由于并非所有的對象都會(huì )使用拷貝構造函數和賦值函數,程序員可能對這兩個(gè)函數有些輕視。請先記住以下的警告,在閱讀正文時(shí)就會(huì )多心: 本章開(kāi)頭講過(guò),如果不主動(dòng)編寫(xiě)拷貝構造函數和賦值函數,編譯器將以“位拷貝”的方式自動(dòng)生成缺省的函數。倘若類(lèi)中含有指針變量,那么這兩個(gè)缺省的函數就隱含了錯誤。以類(lèi)String的兩個(gè)對象a,b為例,假設a.m_data的內容為“hello”,b.m_data的內容為“world”。 現將a賦給b,缺省賦值函數的“位拷貝”意味著(zhù)執行b.m_data = a.m_data。這將造成三個(gè)錯誤:一是b.m_data原有的內存沒(méi)被釋放,造成內存泄露;二是b.m_data和a.m_data指向同一塊內存,a或b任何一方變動(dòng)都會(huì )影響另一方;三是在對象被析構時(shí),m_data被釋放了兩次。 拷貝構造函數和賦值函數非常容易混淆,常導致錯寫(xiě)、錯用??截悩嬙旌瘮凳窃趯ο蟊粍?chuàng )建時(shí)調用的,而賦值函數只能被已經(jīng)存在了的對象調用。以下程序中,第三個(gè)語(yǔ)句和第四個(gè)語(yǔ)句很相似,你分得清楚哪個(gè)調用了拷貝構造函數,哪個(gè)調用了賦值函數嗎? String a(“hello”); String b(“world”); String c = a; // 調用了拷貝構造函數,最好寫(xiě)成 c(a); c = b; // 調用了賦值函數 本例中第三個(gè)語(yǔ)句的風(fēng)格較差,宜改寫(xiě)成String c(a) 以區別于第四個(gè)語(yǔ)句。 請看下面的代碼:
(1) 沒(méi)有重載賦值函數 #include "stdafx.h"
using namespace std;
//shallow && deep copy //deep copy make pointer point to a new place! class Product ...{ public:int* pointer; Product(int i=0) ...{ pointer=new int(i); } //change class variable void change(int i) ...{ *pointer=i; }
// copying constructor Product(const Product &p) ...{ pointer=new int(*p.pointer); } //deconstructor ~Product() ...{ delete pointer; } }; int main() ...{ Product p1(1); Product p2(2); Product p3(3); p2=p3; p3.change(4); cout<<*p2.pointer<<endl;
getchar(); return 0; } //結果輸出4 (2)重載賦值函數 #include "stdafx.h"
using namespace std;
//shallow && deep copy //deep copy make pointer point to a new place! class Product ...{ public:int* pointer; Product(int i=0) ...{ pointer=new int(i); } //change class variable void change(int i) ...{ *pointer=i; }
// copying constructor Product(const Product &p) ...{ pointer=new int(*p.pointer); } //重載= Product& operator=(const Product &p) ...{ if(this!=&p) pointer=new int(*p.pointer); return *this; } //deconstructor ~Product() ...{ delete pointer; } }; int main() ...{ Product p1(1); Product p2(2); Product p3(3); p2=p3; p3.change(4); cout<<*p2.pointer<<endl;
getchar(); return 0; } //輸出3 5. 在拷貝構造函數中使用賦值函數 為了簡(jiǎn)化程序,我們通常在拷貝構造函數中使用賦值函數。 例如: #include <iostream> using namespace std;
class Date...{ int da, mo, yr; public: Date(int d = 0, int m = 0, int y = 0) ...{ cout << "載入構造函數" << endl; da = d; mo = m; yr = y; } Date(const Date &other); Date & operator =(const Date &other); void Show() ...{ cout << mo << "-" << da << "-" << yr << endl; } };
Date::Date(const Date &other) //拷貝構造函數中使用賦值函數 ...{ cout << "載入拷貝構造函數" << endl; *this = other; }
Date & Date::operator =(const Date &other) ...{ cout << "載入賦值函數" << endl; if(this == &other) return *this; da = other.da; mo = other.mo; yr = other.yr; return *this; }
int main() ...{ Date a(1, 3, 6); a.Show(); Date b = a; b.Show(); Date c; c.Show(); c = a; c.Show(); getchar(); return 0; }
程序輸出: 載入構造函數: 3-1-6 載入拷貝構造函數 載入賦值函數 3-1-6 載入構造函數: 0-0-0 載入賦值函數 3-1-6 請注意:程序輸出了兩次“載入賦值函數”,這是因為我們在拷貝構造函數中使用了賦值函數,這樣使程序變得簡(jiǎn)潔。如果把拷貝構造函數改寫(xiě)為: Date::Date(const Date &other) { cout << "載入拷貝構造函數" << endl; da = other.da; mo = other.mo; yr = other.yr; } 則程序將輸出: 載入構造函數: 3-1-6 載入拷貝構造函數 3-1-6 載入構造函數: 0-0-0 載入賦值函數 3-1-6 6. 偷懶的辦法處理拷貝構造函數和賦值函數 如果我們實(shí)在不想編寫(xiě)拷貝構造函數和賦值函數,又不允許別人使用編譯器生成的缺省函數,怎么辦? 偷懶的辦法是:只需將拷貝構造函數和賦值函數聲明為私有函數,不用編寫(xiě)代碼。 例如: class A { … private: A(const A &a); // 私有的拷貝構造函數 A & operator =(const A &a); // 私有的賦值函數 }; 如果有人試圖編寫(xiě)如下程序: A b(a); // 調用了私有的拷貝構造函數 b = a; // 調用了私有的賦值函數 編譯器將指出錯誤,因為外界不可以操作A的私有函數。(引自〈〈高質(zhì)量c++編程指南〉〉)
|