普通對(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>
2
using namespace std;
3
4
class Test
5

{
6
public:
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
}
16
public:
17
int p1;
18
};
19
20
void 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>
2
using namespace std;
3
4
class Internet
5

{
6
public:
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();
36
protected:
37
char name[20];
38
char address[30];
39
char *cname;
40
};
41
void Internet::show()
42

{
43
cout<<name<<":"<<address<<cname<<endl;
44
}
45
void test(Internet ts)
46

{
47
cout<<"載入test函數(shù)"<<endl;
48
}
49
void 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>
2
using namespace std;
3
4
class Internet
5

{
6
public:
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
}
27
protected:
28
char name[20];
29
char address[20];
30
};
31
Internet tp()
32

{
33
Internet b("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com");
34
return b;
35
}
36
void 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>
2
using namespace std;
3
4
class Internet
5

{
6
public:
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
}
22
public:
23
char name[20];
24
char address[20];
25
};
26
27
void 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>
2
using namespace std;
3
4
class Internet
5

{
6
public:
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
}
22
public:
23
char name[20];
24
char address[20];
25
};
26
27
void 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)的)。