拷貝構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它的唯一的一個參數是本類型的一個引用變量,該參數是const類型,不可變的。例如:類X的拷貝構造函數的形式為X(X& x)。
以下情況都會調用拷貝構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化。
如果在類中沒有顯式地聲明一個拷貝構造函數,那么,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝,后面將進行說明。
自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
淺拷貝和深拷貝
在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。
深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源,但復制過程并未復制資源的情況視為淺拷貝。
淺拷貝資源后在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯。
代碼實現:
拷貝構造:
#include <stdio.h>class Test6_1
{
public:Test6_1 (int a){m_a = a;printf ("普通構造函數\n");}// 拷貝構造Test6_1(const Test6_1 &obj){// m_a = obj.m_a;m_a = -100;printf ("拷貝構造被調用\n");}~Test6_1(){printf ("***********析構函數被調用: m_a = %d\n", m_a);}void print(){printf ("a = %d\n", m_a);}
private:int m_a;
};// void test(Test6_1 &t)
void test(Test6_1 t)
{t.print();
}Test6_1 test6_1()
{Test6_1 t(10);return t;
}// 函數返回值是一個對象
int main6_2()
{// 一個函數返回一個對象的時候會創建一個匿名對象,拿返回的那個對象// 對匿名對象進行初始化,會調用拷貝構造// 如果沒有去接收函數的返回值的話,匿名對象會立馬被銷毀// test6_1();// 如果用一個對象去接收函數的返回值,先用函數返回的對象去初始化// 匿名對象,調用一次拷貝構造,然后拿新的對象的名字去命名這個匿名對象// 匿名對象從無名轉成有名// Test6_1 t1 = test6_1();Test6_1 t2(10);// 用函數返回的對象對匿名對象進行初始化,調用拷貝構造// 用匿名對象對 t2 進行賦值,調用 賦值運算符 = t2 = test6_1();// 初始化、構造、賦值int a; // 定義變量int b = 20; // 初始化a = 10; // 賦值printf ("-------------------------\n");return 0;
}int main6_1()
{// 1、直接初始化Test6_1 t1(10);
#if 0Test6_1 t2(t1); // 拿 t1 取初始化 t2// 2、等于號Test6_1 t3 = t1;t3.print();
#endif // 3、當對象作為函數參數傳遞的時候會調用拷貝構造test(t1);return 0;
}
默認構造函數:
#include <stdio.h>class Test7_1
{
public:Test7_1(){}Test7_1(int a){m_a = a;}void print (){printf ("m_a = %d\n", m_a);}
private:int m_a;
};// 如果類中沒有定義任何構造函數,編譯器會自動生成一個無參構造函數,沒有做任何事情
// 如果寫了構造函數,編譯器將不再提供默認的無參構造函數
// 如果還想進行無參構造,需要顯示定義無參構造函數// 如果沒有定義拷貝構造函數,編譯器會自動生成一個拷貝構造函數,
// 會做普通類型數據的復制// 還會生成一個默認的 析構函數
class Test7_2
{
#if 0Test7_2() {}Test7_2(const Test7_2 &obj) {}~Test7_2(){};
#endif
};int main7_1()
{Test7_1 b;Test7_1 a(10);Test7_1 c = a;a.print();c.print();return 0;
}
深拷貝和淺拷貝:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>// 淺拷貝:在拷貝指針的時候只是拷貝了地址,不會進行空間的復制
class Test8_1
{
public:Test8_1(int id, char *name){m_id = id;m_name = (char *)malloc(sizeof(char)*20);strcpy (m_name, name);}~Test8_1(){if (m_name != NULL){free (m_name);m_name = NULL;}printf ("析構被調用**********\n");}void print(){printf ("id = %d, name = %s\n", m_id, m_name);}
private:int m_id;
// char m_name[20];char *m_name;
};// 深拷貝
class Test8_2
{
public:Test8_2(int id, char *name){m_id = id;m_name = (char *)malloc(sizeof(char)*20);strcpy (m_name, name);}// 自己寫拷貝構造函數,避免淺拷貝Test8_2(const Test8_2 &obj){m_id = obj.m_id;m_name = (char *)malloc(sizeof(char)*20);strcpy (m_name, obj.m_name);}~Test8_2(){if (m_name != NULL){free (m_name);m_name = NULL;}printf ("析構被調用**********\n");}void print(){printf ("id = %d, name = %s\n", m_id, m_name);}
private:int m_id;char *m_name;
};class Test8_3
{
private:// 將拷貝構造寫成私有的函數,只需聲明,不需要實現Test8_3(const Test8_3 &ibj);public:Test8_3(int a){m_a = a;}private:int m_a;
};
int main8_3()
{Test8_3 t(10);// Test8_3 t1 = t;return 0;
}int main8_2()
{Test8_2 t1(10, "wang");t1.print();// 調用自己寫的拷貝構造函數,進行深拷貝Test8_2 t2 = t1;t2.print();return 0;
}int main8_1()
{Test8_1 t1(10, "wang");t1.print();// 調用默認的拷貝構造函數Test8_1 t2 = t1;//t2.print();return 0;
}