您好,登錄后才能下訂單哦!
對于普通類型的對象來說,它們之間的復制是很簡單的,例如:
int a=88;
int b=a;
double f=3.12;
double d(f);
而類對象與普通對象不同,類對象內部結構一般較為復雜,存在各種數據成員。
#include <iostream>
using namespace std;
class Test
{
private:
int a,b;
public:
Test(int x, int y) //提供的形式參數,是為了給數據成員直接初始化的
{
a=x;
b=y;
}
Test(const Test& C) //復制構造函數,提供一個同類型對象作為參數
{
a=C.a;
b=C.b;
}
void show ()
{
cout<<a<<" "<<b<<endl;
}
};
int main()
{
Test a(100,10); //執行構造函數Test::Test(int x, int y)
Test b(a); //執行構造函數Test::Test(const Test& C)
Test c=a; //也執行構造函數Test::Test(const Test& C)
b.show();
c.show();
return 0;
}
運行程序,屏幕輸出兩行100 10。
從以上代碼的運行結果可以看出,系統在聲明對象b和c時,完成了由對象a的復制。
復制構造函數,就類對象而言,相同類型的類對象是通過復制構造函數來完成整個復制過程的。
上例中的Test::Test(const Test& C),就是我們自定義的復制構造函數。可見,復制構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它的唯一的一個參數是本類型的一個引用變量,該參數是const類型,用來約束作為參數的對象在構造新對象中是不能被改變的。
當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,復制構造函數就會被自動調用。也就是說,當類的對象需要復制時,復制構造函數將會被調用。以下情況都會調用復制構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化。
如果在類中沒有顯式地聲明一個復制構造函數,那么,編譯器將會自動生成一個默認的復制構造函數,該構造函數完成對象之間的淺復制,后面將進行說明。
自定義復制構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的復制構造函數,提高源碼效率。
淺復制和深復制
所謂淺復制,如同上面出現過的構造函數中處理的一樣,直接為數據成員賦值即可。在很多情況下,這是可以的。創建新的對象,要為對象的數據成員分配存儲空間,直接賦值就將值保存在相應的空間中。然而,這種淺復制,卻并不能通行天下,下面的程序中,淺復制帶來了問題。
#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:
int a;
char *str;
public:
Test(int b, char *s)
{
a=b;
strcpy(str,s); //肇事地點,但不是禍端
}
Test(const Test& C)
{
a=C.a;
strcpy(str,C.str);
}
void show ()
{
cout<<a<<","<<str<<endl;
}
};
int main()
{
Test a(100,"hello");
Test b(a);
a.show();
b.show();
return 0;
}
程序運行中,會彈出一個窗口:程序的執行意外停止了。面對這個窗口,我們應該有感覺,這和使用指針不當有關系。
我們從main函數看起。
當程序執行到第28行Test a(100,"hello");時,對象a的數據成員a獲得實際參數100的值,而數據成員str,即指針,是個隨機地址值(指針的值,非指針指向的值)!在程序中,試圖通過strcpy(str,s);將形式參數s指向的字符串"hello",復制到str所指向的那個位置,而那個位置,其地址并不是經過系統分配來的,這是個危險的操作。在這里,str這樣未經過分配的地址(指針),被稱為“野指針”。
在執行第29行Test b(a);時,同樣的事情還要發生。
str這樣的野指針是多么的霸道!在有的系統里,這樣的行為是被接受的,可以想到其有多危險。有些系統中,不允許這樣的事情發生的。于是,上面的程序在codeblock中調試會出錯。這個錯來的好。
解決這樣的問題的方法,就是在構造函數中,要為指針類型的成員,分配專門的空間。以這條規則構建的復制,稱作為深復制!
上面的程序,改寫為:
#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:
int a;
char *str;
public:
Test(int b, char *s)
{
a=b;
str=new char[strlen(s)+1]; //分配str指向的空間,其長度根據s指向的字符串定。為何加1?字符串結束要用\0
strcpy(str,s); //前一程序的肇事地點,禍端已經由上一句摘除
}
Test(const Test& C)
{
a=C.a;
str=new char[strlen(C.str)+1]; //同上,這樣str就不是野指針了
strcpy(str,C.str);
}
~Test()
{
delete []str;
}
void show ()
{
cout<<a<<","<<str<<endl;
}
};
int main()
{
Test a(100,"hello");
Test b(a);
a.show();
b.show();
return 0;
}
```
好了,a和b對象的str成員,明確地給分配了空間,他們再不是野指針了。因為明確地分配了空間,析構函數中要釋放對應的空間。我們不能用野指針,當然,也不能對象要撤銷了,還占著空間不放,做事不能這樣不厚道。
深復制就體現在第13和第19行分配指針指向的空間,這段空間的地址,也將是指針的值(分清指針的值和指針指向的值)。
下面再給一個例子,類A的數據成員可以保存len個整型數據。類中的數據成員arrayAddr是指向整型的指針,可以作為一個一元數組的起始地址。這個類有指針數據成員,構造函數的定義中,必須要采用深復制的方法,第16行體現了這一點。另外,析構函數中完成了對分配的空間的釋放
#include<iostream>
using namespace std;
class A
{
private:
int arrayAddr;//保存一個有len個整型元素的數組的首地址
int len; //記錄動態數組的長度
public:
A(int a, int n);
~A();
int sum();
};
A::A(int *a, int n)
{
len=n;
arrayAddr=new int[n]; //為指針數據成員分配空間,注意,沒有上面例子中加1那回事
for(int i=0; i<n; i++) //逐個地將a指向的值逐個地復制過來
{
arrayAddr[i]=a[i];
}
}
//析構函數的類外定義,釋放指針型數據a所指向的空間
A::~A()
{
delete [] arrayAddr;
}
int A::sum() //獲得a指向的數組中下標為i的元素的值
{
int s=0;
for(int i=0; i<len; i++) //逐個地將a指向的值逐個地復制過來
{
s+=arrayAddr[i];
}
return s;
}
int main(){
int b[10]= {75, 99, 90, 93, 38, 15, 5, 7, 52, 4};
A r1(b,10);
cout<<"和:"<<r1.sum()<<endl;
int c[15] = {18,68,10,52,3,19,12,100,56,96,95,97,1,4,93};
A r2(c,15);
cout<<"和:"<<r2.sum()<<endl;
return 0;
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。