對於普通類型的對象來說,它們之間的復制是很簡單的,例如:
int a=88;
int b=a;
而類對象與普通對象不同,類對象內部結構一般較為復雜,存在各種成員變量。
本文地址:http://www.cnblogs.com/archimedes/p/cpp-copy-constructor.html,轉載請注明源地址。
1、對象的定義形式
C++支持兩種定義形式:直接初始化和復制初始化,復制初始化采用=符號,直接初始化將初始化式放在圓括號中
用於類類型對象時,直接初始化直接調用與實參匹配的構造函數,復制初始化總是調用拷貝構造函數,復制初始化首先使用指定構造函數創建一個臨時對象,然後拷貝構造函數將那個臨時對象拷貝到正在創建的對象
string null_book="9-454-45546"; //復制初始化 string dots(10,'.'); //直接初始化 string empty_copy=string(); //復制初始化 string empty_direct; //直接初始化
通常,直接初始化和復制初試化只在低級別優化上存在差異,但是對於不支持復制的類型,或者使用非explicit構造函數的時候,它們就有本質的區別:
ifstream file1("filename"); //ok ifstream file2="filename"; //error! 復制構造函數是私有的
2、形參與返回值
當形參是非引用類型的時候,將復制實參的值,以非引用類型作為返回值的時候,將返回return語句中值的副本
3、初始化容器元素
復制初始化函數可用於初始化順序容器中的元素,容器的這種構造方式使用了默認構造函數和拷貝構造函數:
vector<string> svec(5);
4、構造函數與數組元素
如果沒有為類類型數組提供元素的初始化形式,則將使用默認構造函數初始化每個元素,然而,如果使用常規的花括號括住的數組初始化列表來提供顯示元素的初始化式,則使用復制初始化來初始化每個元素:
A a[]={ string("hdsf"), string("weiyrw"), string("cxvbc"), A() };
如果沒有定義拷貝構造函數,編譯器會為我們合成一個,即使我們定義了其他的構造函數,也會合成拷貝構造函數。合成拷貝構造函數的行為是,執行逐個成員的初始化。編譯器將現有對象的每個非static成員,依次復制到正創建的對象,合成拷貝構造函數直接復制內置類型成員的值,類類型成員使用該類的拷貝構造函數進行復制,數組成員使用合成拷貝構造函數將復制數組的每一個元素
class A{ private: string s; int n; double b; }; A::A(const A &a):s(a.s),n(a.n),b(a.b){}
定義自己的拷貝構造函數
class A{ public: A(); A(const &A); //...... };
下面看一個類對象拷貝的簡單例子:
#include<iostream> using namespace std; class A{ private: int n; public: A(int m) { n=m;} void print() { cout<<n<<endl; } }; int main() { A a(100); A a1=a; a1.print(); return 0; }
運行程序,屏幕輸出100。從以上代碼的運行結果可以看出,系統為對象a1分配了內存並完成了與對象a的拷貝過程。就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的。下面舉例說明拷貝函數的工作過程:
#include<iostream> using namespace std; class A{ private: int n; public: A(int m) { n=m;} A(const A& c) {n=c.n;} void print() { cout<<n<<endl; } }; int main() { A a(100); A a1=a; a1.print(); return 0; }
A(const A& c)就是我們自定義的拷貝構造函數。可見,拷貝構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它的唯一的一個參數是本類型的一個引用變量,該參數是const類型,不可變的。例如:類X的拷貝構造函數的形式為X(X& x)。
當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用。也就是說,當類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化
如果在類中沒有顯式地聲明一個拷貝構造函數,那麼,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝,後面將進行說明。自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象裡的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。下面舉個深拷貝的例子:
#include<iostream> #include<cstring> #include<string> using namespace std; class A{ private: int n; char *s; public: A(int m, char* str) { n=m; s=new char[m]; strcpy(s,str); } A(const A& c) { n=c.n; s=new char[n]; //深拷貝 if(s!=NULL) strcpy(s,c.s); } void print() { cout<<s<<endl; } ~A() { delete s; } }; int main() { A a(100,"string"); A a1=a; a1.print(); return 0; }
深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源,但復制過程並未復制資源的情況視為淺拷貝。
淺拷貝資源後在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯。
拷貝構造函數的名稱必須與類名稱一致,函數的形式參數是本類型的一個引用變量,且必須是引用。當用一個已經初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用,如果你沒有自定義拷貝構造函數的時候,系統將會提供給一個默認的拷貝構造函數來完成這個過程
禁止復制
拷貝構造函數是私有的,將不允許用戶代碼復制該類類型的對象,但是類的友元和成員仍可以進行復制,如果連類的友元和成員也禁止,可以聲明一個private構造函數但是不對其定義