普通對象和類對象同為對象,他們之間的特性有相似之處也有不同之處,類對象內部存在成員變量,而普通對象是沒有的,當同樣的復制方法發(fā)生在不同的對象上的時候,那么系統(tǒng)對他們進行的操作也是不一樣的,就類對象而言,相同類型的類對象是通過
拷貝構造函數(shù)來完成整個復制過程的,在上面的代碼中,我們并沒有看到拷貝構造函數(shù),同樣完成了復制工作,這又是為什么呢?因為當一個類沒有自定義的拷貝構造函數(shù)的時候系統(tǒng)會自動提供一個
默認的拷貝構造函數(shù),來完成復制工作。
下面,我們?yōu)榱苏f明情況,就普通情況而言(以上面的代碼為例),我們來自己定義一個與系統(tǒng)默認拷貝構造函數(shù)一樣的拷貝構造函數(shù),看看它的內部是如何工作的!
代碼如下:
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)//這里就是自定義的拷貝構造函數(shù)
12
{
13
cout<<"進入copy構造函數(shù)"<<endl;
14
p1=c_t.p1;//這句如果去掉就不能完成復制工作了,此句復制過程的核心語句
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)就是我們自定義的拷貝構造函數(shù),拷貝構造函數(shù)的名稱必須與類名稱一致,函數(shù)的形式參數(shù)是本類型的一個引用變量,
且必須是引用。
當用一個已經(jīng)初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數(shù)就會被自動調用,如果你沒有自定義拷貝構造函數(shù)的時候系統(tǒng)將會提供給一個默認的拷貝構造函數(shù)來完成這個過程,上面代碼的復制核心語句就是通過Test(Test &c_t)拷貝構造函數(shù)內的p1=c_t.p1;語句完成的。如果取掉這句代碼,那么b對象的p1屬性將得到一個未知的隨機值;
下面我們來討論一下關于淺拷貝和深拷貝的問題。
就上面的代碼情況而言,很多人會問到,既然系統(tǒng)會自動提供一個默認的拷貝構造函數(shù)來處理復制,那么我們沒有意義要去自定義拷貝構造函數(shù)呀,對,就普通情況而言這的確是沒有必要的,但在某寫狀況下,類體內的成員是需要開辟動態(tài)開辟堆內存的,如果我們不自定義拷貝構造函數(shù)而讓系統(tǒng)自己處理,那么就會導致堆內存的所屬權產(chǎn)生混亂,試想一下,已經(jīng)開辟的一端堆地址原來是屬于對象a的,由于復制過程發(fā)生,b對象取得是a已經(jīng)開辟的堆地址,一旦程序產(chǎn)生析構,釋放堆的時候,計算機是不可能清楚這段地址是真正屬于誰的,當連續(xù)發(fā)生兩次析構的時候就出現(xiàn)了運行錯誤。
為了更詳細的說明問題,請看如下的代碼。
1
#include <iostream>
2
using namespace std;
3
4
class Internet
5

{
6
public:
7
Internet(char *name,char *address)
8
{
9
cout<<"載入構造函數(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構造函數(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<<"載入析構函數(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("中國軟件開發(fā)實驗室","www.cndev-lab.com");
52
Internet b = a;
53
b.show();
54
test(b);
55
}
56
57
上面代碼就演示了深拷貝的問題,對對象b的cname屬性采取了新開辟內存的方式避免了內存歸屬不清所導致析構釋放空間時候的錯誤,最后我必須提一下,對于上面的程序我的解釋并不多,就是希望讀者本身運行程序觀察變化,進而深刻理解。
拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統(tǒng)資源),當這個類的對象發(fā)生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源但復制過程并未復制資源的情況視為淺拷貝。
淺拷貝資源后在釋放資源的時候會產(chǎn)生資源歸屬不清的情況導致程序運行出錯,這點尤其需要注意!
以前我們的教程中討論過函數(shù)返回對象產(chǎn)生臨時變量的問題,接下來我們來看一下在函數(shù)中返回自定義類型對象是否也遵循此規(guī)則產(chǎ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<<"載入構造函數(shù)"<<endl;
14
strcpy(Internet::name,name);
15
}
16
Internet(Internet &temp)
17
{
18
cout<<"載入COPY構造函數(shù)"<<endl;
19
strcpy(Internet::name,temp.name);
20
cin.get();
21
}
22
~Internet()
23
{
24
cout<<"載入析構函數(shù)!";
25
cin.get();
26
}
27
protected:
28
char name[20];
29
char address[20];
30
};
31
Internet tp()
32

{
33
Internet b("中國軟件開發(fā)實驗室","www.cndev-lab.com");
34
return b;
35
}
36
void main()
37

{
38
Internet a;
39
a=tp();
40
}
41
42
從上面的代碼運行結果可以看出,程序一共載入過析構函數(shù)三次,證明了由函數(shù)返回自定義類型對象同樣會產(chǎn)生臨時變量,事實上對象a得到的就是這個臨時Internet類類型對象temp的值。
這一下節(jié)的內容我們來說一下無名對象。
利用無名對象初始化對象系統(tǒng)不會不調用拷貝構造函數(shù)。
那么什么又是無名對象呢?
很簡單,如果在上面程序的main函數(shù)中有:
Internet ("中國軟件開發(fā)實驗室","www.cndev-lab.com");
這樣的一句語句就會產(chǎn)生一個無名對象,無名對象會調用構造函數(shù)但利用無名對象初始化對象系統(tǒng)不會不調用拷貝構造函數(shù)!
下面三段代碼是很見到的三種利用無名對象初始化對象的例子。
1
#include <iostream>
2
using namespace std;
3
4
class Internet
5

{
6
public:
7
Internet(char *name,char *address)
8
{
9
cout<<"載入構造函數(shù)"<<endl;
10
strcpy(Internet::name,name);
11
}
12
Internet(Internet &temp)
13
{
14
cout<<"載入COPY構造函數(shù)"<<endl;
15
strcpy(Internet::name,temp.name);
16
cin.get();
17
}
18
~Internet()
19
{
20
cout<<"載入析構函數(shù)!";
21
}
22
public:
23
char name[20];
24
char address[20];
25
};
26
27
void main()
28

{
29
Internet a=Internet("中國軟件開發(fā)實驗室","www.cndev-lab.com");
30
cout<<a.name;
31
cin.get();
32
}
33
34
上面代碼的運行結果有點“出人意料”,從思維邏輯上說,當無名對象創(chuàng)建了后,是應該調用自定義拷貝構造函數(shù),或者是默認拷貝構造函數(shù)來完成復制過程的,但事實上系統(tǒng)并沒有這么做,因為無名對象使用過后在整個程序中就失去了作用,對于這種情況c++會把代碼看成是:
Internet a("中國軟件開發(fā)實驗室",www.cndev-lab.com);
省略了創(chuàng)建無名對象這一過程,所以說不會調用拷貝構造函數(shù)。
最后讓我們來看看引用無名對象的情況。
1
#include <iostream>
2
using namespace std;
3
4
class Internet
5

{
6
public:
7
Internet(char *name,char *address)
8
{
9
cout<<"載入構造函數(shù)"<<endl;
10
strcpy(Internet::name,name);
11
}
12
Internet(Internet &temp)
13
{
14
cout<<"載入COPY構造函數(shù)"<<endl;
15
strcpy(Internet::name,temp.name);
16
cin.get();
17
}
18
~Internet()
19
{
20
cout<<"載入析構函數(shù)!";
21
}
22
public:
23
char name[20];
24
char address[20];
25
};
26
27
void main()
28

{
29
Internet &a=Internet("中國軟件開發(fā)實驗室","www.cndev-lab.com");
30
cout<<a.name;
31
cin.get();
32
}
33
34
引用本身是對象的別名,和復制并沒有關系,所以不會調用拷貝構造函數(shù),但要注意的是,在c++看來:
Internet &a=Internet("中國軟件開發(fā)實驗室","www.cndev-lab.com");
是等價與:
Internet a("中國軟件開發(fā)實驗室","www.cndev-lab.com");
的,注意觀察調用析構函數(shù)的位置(
這種情況是在main()外調用,而無名對象本身是在main()內析構的)。