普通對(duì)象和類對(duì)象同為對(duì)象,他們之間的特性有相似之處也有不同之處,類對(duì)象內(nèi)部存在成員變量,而普通對(duì)象是沒(méi)有的,當(dāng)同樣的復(fù)制方法發(fā)生在不同的對(duì)象上的時(shí)候,那么系統(tǒng)對(duì)他們進(jìn)行的操作也是不一樣的,就類對(duì)象而言,相同類型的類對(duì)象是通過(guò)拷貝構(gòu)造函數(shù)來(lái)完成整個(gè)復(fù)制過(guò)程的,在上面的代碼中,我們并沒(méi)有看到拷貝構(gòu)造函數(shù),同樣完成了復(fù)制工作,這又是為什么呢?因?yàn)楫?dāng)一個(gè)類沒(méi)有自定義的拷貝構(gòu)造函數(shù)的時(shí)候系統(tǒng)會(huì)自動(dòng)提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù),來(lái)完成復(fù)制工作。

  下面,我們?yōu)榱苏f(shuō)明情況,就普通情況而言(以上面的代碼為例),我們來(lái)自己定義一個(gè)與系統(tǒng)默認(rèn)拷貝構(gòu)造函數(shù)一樣的拷貝構(gòu)造函數(shù),看看它的內(nèi)部是如何工作的!

  代碼如下:
 1#include <iostream>  
 2using namespace std;  
 3  
 4class Test  
 5{  
 6public:  
 7    Test(int temp)  
 8    {  
 9        p1=temp;  
10    }
  
11    Test(Test &c_t)//這里就是自定義的拷貝構(gòu)造函數(shù)  
12    {  
13        cout<<"進(jìn)入copy構(gòu)造函數(shù)"<<endl;  
14        p1=c_t.p1;//這句如果去掉就不能完成復(fù)制工作了,此句復(fù)制過(guò)程的核心語(yǔ)句  
15    }
  
16public:  
17    int p1;  
18}
;  
19  
20void main()  
21{  
22    Test a(99);  
23    Test b=a;  
24    cout<<b.p1;  
25    cin.get();  
26}

27
28
上面代碼中的Test(Test &c_t)就是我們自定義的拷貝構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)的名稱必須與類名稱一致,函數(shù)的形式參數(shù)是本類型的一個(gè)引用變量,必須是引用

  當(dāng)用一個(gè)已經(jīng)初始化過(guò)了的自定義類類型對(duì)象去初始化另一個(gè)新構(gòu)造的對(duì)象的時(shí)候,拷貝構(gòu)造函數(shù)就會(huì)被自動(dòng)調(diào)用,如果你沒(méi)有自定義拷貝構(gòu)造函數(shù)的時(shí)候系統(tǒng)將會(huì)提供給一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)來(lái)完成這個(gè)過(guò)程,上面代碼的復(fù)制核心語(yǔ)句就是通過(guò)Test(Test &c_t)拷貝構(gòu)造函數(shù)內(nèi)的p1=c_t.p1;語(yǔ)句完成的。如果取掉這句代碼,那么b對(duì)象的p1屬性將得到一個(gè)未知的隨機(jī)值;
下面我們來(lái)討論一下關(guān)于淺拷貝和深拷貝的問(wèn)題。

  就上面的代碼情況而言,很多人會(huì)問(wèn)到,既然系統(tǒng)會(huì)自動(dòng)提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)來(lái)處理復(fù)制,那么我們沒(méi)有意義要去自定義拷貝構(gòu)造函數(shù)呀,對(duì),就普通情況而言這的確是沒(méi)有必要的,但在某寫(xiě)狀況下,類體內(nèi)的成員是需要開(kāi)辟動(dòng)態(tài)開(kāi)辟堆內(nèi)存的,如果我們不自定義拷貝構(gòu)造函數(shù)而讓系統(tǒng)自己處理,那么就會(huì)導(dǎo)致堆內(nèi)存的所屬權(quán)產(chǎn)生混亂,試想一下,已經(jīng)開(kāi)辟的一端堆地址原來(lái)是屬于對(duì)象a的,由于復(fù)制過(guò)程發(fā)生,b對(duì)象取得是a已經(jīng)開(kāi)辟的堆地址,一旦程序產(chǎn)生析構(gòu),釋放堆的時(shí)候,計(jì)算機(jī)是不可能清楚這段地址是真正屬于誰(shuí)的,當(dāng)連續(xù)發(fā)生兩次析構(gòu)的時(shí)候就出現(xiàn)了運(yùn)行錯(cuò)誤。

  為了更詳細(xì)的說(shuō)明問(wèn)題,請(qǐng)看如下的代碼。
 1#include <iostream>  
 2using namespace std;  
 3  
 4class Internet  
 5{  
 6public:  
 7    Internet(char *name,char *address)  
 8    {  
 9        cout<<"載入構(gòu)造函數(shù)"<<endl;  
10        strcpy(Internet::name,name);  
11        strcpy(Internet::address,address);  
12        cname=new char[strlen(name)+1];  
13        if(cname!=NULL)  
14        {  
15            strcpy(Internet::cname,name);  
16        }
  
17    }
  
18    Internet(Internet &temp)  
19    {  
20        cout<<"載入COPY構(gòu)造函數(shù)"<<endl;  
21        strcpy(Internet::name,temp.name);  
22        strcpy(Internet::address,temp.address);  
23        cname=new char[strlen(name)+1];//這里注意,深拷貝的體現(xiàn)!  
24        if(cname!=NULL)  
25        {  
26            strcpy(Internet::cname,name);  
27        }
  
28    }
  
29    ~Internet()  
30    {  
31        cout<<"載入析構(gòu)函數(shù)!";  
32        delete[] cname;  
33        cin.get();  
34    }
  
35    void show();  
36protected:  
37    char name[20];  
38    char address[30];  
39    char *cname;  
40}
;  
41void Internet::show()  
42{  
43    cout<<name<<":"<<address<<cname<<endl;  
44}
  
45void test(Internet ts)  
46{  
47    cout<<"載入test函數(shù)"<<endl;  
48}
  
49void main()  
50{  
51    Internet a("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");  
52    Internet b = a;  
53    b.show();  
54    test(b);  
55}

56
57
上面代碼就演示了深拷貝的問(wèn)題,對(duì)對(duì)象b的cname屬性采取了新開(kāi)辟內(nèi)存的方式避免了內(nèi)存歸屬不清所導(dǎo)致析構(gòu)釋放空間時(shí)候的錯(cuò)誤,最后我必須提一下,對(duì)于上面的程序我的解釋并不多,就是希望讀者本身運(yùn)行程序觀察變化,進(jìn)而深刻理解。

  拷貝和淺拷貝的定義可以簡(jiǎn)單理解成:如果一個(gè)類擁有資源(堆,或者是其它系統(tǒng)資源),當(dāng)這個(gè)類的對(duì)象發(fā)生復(fù)制過(guò)程的時(shí)候,這個(gè)過(guò)程就可以叫做深拷貝,反之對(duì)象存在資源但復(fù)制過(guò)程并未復(fù)制資源的情況視為淺拷貝。


  淺拷貝資源后在釋放資源的時(shí)候會(huì)產(chǎn)生資源歸屬不清的情況導(dǎo)致程序運(yùn)行出錯(cuò),這點(diǎn)尤其需要注意!

  以前我們的教程中討論過(guò)函數(shù)返回對(duì)象產(chǎn)生臨時(shí)變量的問(wèn)題,接下來(lái)我們來(lái)看一下在函數(shù)中返回自定義類型對(duì)象是否也遵循此規(guī)則產(chǎn)生臨時(shí)對(duì)象!

先運(yùn)行下列代碼:
 1#include <iostream>  
 2using namespace std;  
 3  
 4class Internet  
 5{  
 6public:  
 7    Internet()  
 8    {  
 9          
10    }
;  
11    Internet(char *name,char *address)  
12    {  
13        cout<<"載入構(gòu)造函數(shù)"<<endl;  
14        strcpy(Internet::name,name);  
15    }
  
16    Internet(Internet &temp)  
17    {  
18        cout<<"載入COPY構(gòu)造函數(shù)"<<endl;  
19        strcpy(Internet::name,temp.name);  
20        cin.get();  
21    }
  
22    ~Internet()  
23    {  
24        cout<<"載入析構(gòu)函數(shù)!";  
25        cin.get();  
26    }
  
27protected:  
28    char name[20];  
29    char address[20];  
30}
;  
31Internet tp()  
32{  
33    Internet b("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");  
34    return b;  
35}
  
36void main()  
37{  
38    Internet a;  
39    a=tp();  
40}

41
42

 從上面的代碼運(yùn)行結(jié)果可以看出,程序一共載入過(guò)析構(gòu)函數(shù)三次,證明了由函數(shù)返回自定義類型對(duì)象同樣會(huì)產(chǎn)生臨時(shí)變量,事實(shí)上對(duì)象a得到的就是這個(gè)臨時(shí)Internet類類型對(duì)象temp的值。

  這一下節(jié)的內(nèi)容我們來(lái)說(shuō)一下無(wú)名對(duì)象。

  利用無(wú)名對(duì)象初始化對(duì)象系統(tǒng)不會(huì)不調(diào)用拷貝構(gòu)造函數(shù)。

  那么什么又是無(wú)名對(duì)象呢?

  很簡(jiǎn)單,如果在上面程序的main函數(shù)中有:

  Internet ("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");

  這樣的一句語(yǔ)句就會(huì)產(chǎn)生一個(gè)無(wú)名對(duì)象,無(wú)名對(duì)象會(huì)調(diào)用構(gòu)造函數(shù)但利用無(wú)名對(duì)象初始化對(duì)象系統(tǒng)不會(huì)不調(diào)用拷貝構(gòu)造函數(shù)!

  下面三段代碼是很見(jiàn)到的三種利用無(wú)名對(duì)象初始化對(duì)象的例子。

 1#include <iostream>  
 2using namespace std;  
 3  
 4class Internet  
 5{  
 6public:  
 7    Internet(char *name,char *address)  
 8    {  
 9        cout<<"載入構(gòu)造函數(shù)"<<endl;  
10        strcpy(Internet::name,name);  
11    }
  
12    Internet(Internet &temp)  
13    {  
14        cout<<"載入COPY構(gòu)造函數(shù)"<<endl;  
15        strcpy(Internet::name,temp.name);  
16        cin.get();  
17    }
  
18    ~Internet()  
19    {  
20        cout<<"載入析構(gòu)函數(shù)!";  
21    }
  
22public:  
23    char name[20];  
24    char address[20];  
25}
;  
26  
27void main()  
28{  
29    Internet a=Internet("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");  
30    cout<<a.name;  
31    cin.get();  
32}

33
34

上面代碼的運(yùn)行結(jié)果有點(diǎn)“出人意料”,從思維邏輯上說(shuō),當(dāng)無(wú)名對(duì)象創(chuàng)建了后,是應(yīng)該調(diào)用自定義拷貝構(gòu)造函數(shù),或者是默認(rèn)拷貝構(gòu)造函數(shù)來(lái)完成復(fù)制過(guò)程的,但事實(shí)上系統(tǒng)并沒(méi)有這么做,因?yàn)闊o(wú)名對(duì)象使用過(guò)后在整個(gè)程序中就失去了作用,對(duì)于這種情況c++會(huì)把代碼看成是:

Internet a("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室",www.cndev-lab.com);

  省略了創(chuàng)建無(wú)名對(duì)象這一過(guò)程,所以說(shuō)不會(huì)調(diào)用拷貝構(gòu)造函數(shù)。

最后讓我們來(lái)看看引用無(wú)名對(duì)象的情況。
 1#include <iostream>    
 2using namespace std;    
 3    
 4class Internet    
 5{    
 6public:    
 7    Internet(char *name,char *address)    
 8    {    
 9        cout<<"載入構(gòu)造函數(shù)"<<endl;    
10        strcpy(Internet::name,name);    
11    }
    
12    Internet(Internet &temp)    
13    {    
14        cout<<"載入COPY構(gòu)造函數(shù)"<<endl;    
15        strcpy(Internet::name,temp.name);    
16        cin.get();    
17    }
    
18    ~Internet()    
19    {    
20        cout<<"載入析構(gòu)函數(shù)!";    
21    }
    
22public:    
23    char name[20];    
24    char address[20];    
25}
;    
26    
27void main()    
28{    
29    Internet &a=Internet("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");    
30    cout<<a.name;  
31    cin.get();    
32}

33
34
引用本身是對(duì)象的別名,和復(fù)制并沒(méi)有關(guān)系,所以不會(huì)調(diào)用拷貝構(gòu)造函數(shù),但要注意的是,在c++看來(lái):

Internet &a=Internet("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");

  是等價(jià)與:

Internet a("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");

  的,注意觀察調(diào)用析構(gòu)函數(shù)的位置(這種情況是在main()外調(diào)用,而無(wú)名對(duì)象本身是在main()內(nèi)析構(gòu)的)。