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