定義:
class String
{
public:
String(const char *str = NULL); // 普通構(gòu)造函數(shù)
String(const String &other); // 拷貝構(gòu)造函數(shù)
~ String(void); // 析構(gòu)函數(shù)
String & operate =(const String &other); // 賦值函數(shù)
private:
char *m_data; // 用于保存字符串
};
普通構(gòu)造:
// String的普通構(gòu)造函數(shù)
String::String(const char *str)
{
if(str==NULL)
{
m_data = new char[1];
*m_data = ‘\0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data, str);
}
}
注意在聲明時(shí),才定義的參數(shù),且賦初值為NULL。在拷貝函數(shù)內(nèi)部,也進(jìn)行了是否為NULL的檢查,進(jìn)行特殊處理。
析構(gòu)函數(shù):
// String的析構(gòu)函數(shù)
String::~String(void)
{
delete [] m_data;
// 由于m_data是內(nèi)部數(shù)據(jù)類型,也可以寫成 delete m_data;
}
內(nèi)部數(shù)據(jù)類型,delete
外部數(shù)據(jù)類型,[ ] delete
拷貝構(gòu)造函數(shù)與賦值函數(shù):
拷貝函數(shù):
位拷貝:編譯器以此方式自動(dòng)生成缺省的函數(shù),比如構(gòu)造拷貝和賦值拷貝。但是,如果函數(shù)中還有指針變量,那么缺省的函數(shù)就隱含了錯(cuò)誤。
例如:
以類String的兩個(gè)對(duì)象a,b為例,假設(shè)a.m_data的內(nèi)容為“hello”,b.m_data的內(nèi)容為“world”。
現(xiàn)將a賦給b,缺省賦值函數(shù)的“位拷貝”意味著執(zhí)行b.m_data = a.m_data。
這將造成三個(gè)錯(cuò)誤:
一是b.m_data原有的內(nèi)存沒被釋放,造成內(nèi)存泄露;
二是b.m_data和a.m_data指向同一塊內(nèi)存,a或b任何一方變動(dòng)都會(huì)影響另一方;
三是在對(duì)象被析構(gòu)時(shí),m_data被釋放了兩次。
混淆:
String a(“hello”);
String b(“world”);
String c = a; // 調(diào)用了拷貝構(gòu)造函數(shù),最好寫成 c(a);
c = b; // 調(diào)用了賦值函數(shù)
例子:
// 拷貝構(gòu)造函數(shù)
String::String(const String &other)
{
// 允許操作other的私有成員m_data
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
}
賦值函數(shù):
// 賦值函數(shù)
String & String::operate =(const String &other)
{
// (1) 檢查自賦值
if(this == &other)
return *this;
// (2) 釋放原有的內(nèi)存資源
delete [] m_data;
// (3)分配新的內(nèi)存資源,并復(fù)制內(nèi)容
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
// (4)返回本對(duì)象的引用
return *this;
}
賦值函數(shù)的四個(gè)步驟:
(1) 檢查自賦值
不會(huì)寫出a = a的自賦值語句,但間接自賦值有可能發(fā)生。
|
// 內(nèi)容自賦值
b = a;
…
c = b;
…
a = c;
|
// 地址自賦值
b = &a;
…
a = *b;
|
防止:
if(this == &other)
錯(cuò)寫成為
if( *this == other)
(2) 釋放原有的內(nèi)存資源
(3) 分配新的內(nèi)存資源,并復(fù)制內(nèi)容
(4) 返回本對(duì)象的引用
F:拷貝構(gòu)造和普通構(gòu)造的區(qū)別?
Q:拷貝構(gòu)造的參數(shù)是“引用”,引用不可能是NULL;普通構(gòu)造函數(shù)參數(shù)是“指針”,需要類型檢查,判斷是否是NULL。
類String的賦值函數(shù)比構(gòu)造函數(shù)復(fù)雜得多,分四步實(shí)現(xiàn):
(1)第一步,檢查自賦值。你可能會(huì)認(rèn)為多此一舉,難道有人會(huì)愚蠢到寫出 a = a 這樣的自賦值語句!的確不會(huì)。但是間接的自賦值仍有可能出現(xiàn),例如
|
// 內(nèi)容自賦值
b = a;
…
c = b;
…
a = c;
|
// 地址自賦值
b = &a;
…
a = *b;
|
也許有人會(huì)說:“即使出現(xiàn)自賦值,我也可以不理睬,大不了化點(diǎn)時(shí)間讓對(duì)象復(fù)制自己而已,反正不會(huì)出錯(cuò)!”
他真的說錯(cuò)了。看看第二步的delete,自殺后還能復(fù)制自己?jiǎn)幔克裕绻l(fā)現(xiàn)自賦值,應(yīng)該馬上終止函數(shù)。注意不要將檢查自賦值的if語句
if(this == &other)
錯(cuò)寫成為
if( *this == other)
(2)第二步,用delete釋放原有的內(nèi)存資源。如果現(xiàn)在不釋放,以后就沒機(jī)會(huì)了,將造成內(nèi)存泄露。
(3)第三步,分配新的內(nèi)存資源,并復(fù)制字符串。注意函數(shù)strlen返回的是有效字符串長度,不包含結(jié)束符‘\0’。函數(shù)strcpy則連‘\0’一起復(fù)制。
(4)第四步,返回本對(duì)象的引用,目的是為了實(shí)現(xiàn)象 a = b = c 這樣的鏈?zhǔn)奖磉_(dá)。注意不要將 return *this 錯(cuò)寫成 return this 。那么能否寫成return other 呢?效果不是一樣嗎?
不可以!因?yàn)槲覀儾恢绤?shù)other的生命期。有可能other是個(gè)臨時(shí)對(duì)象,在賦值結(jié)束后它馬上消失,那么return other返回的將是垃圾。
08-22
如果不想讓別人使用編譯器編寫構(gòu)造拷貝和賦值函數(shù),可以聲明為私有:
class A
{ …
private:
A(const A &a); // 私有的拷貝構(gòu)造函數(shù)
A & operate =(const A &a); // 私有的賦值函數(shù)
};
如果有人試圖編寫如下程序:
A b(a); // 調(diào)用了私有的拷貝構(gòu)造函數(shù)
b = a; // 調(diào)用了私有的賦值函數(shù)
編譯器將指出錯(cuò)誤,因?yàn)橥饨绮豢梢圆僮?/span>A的私有函數(shù)。
但是怎樣才能使用構(gòu)造拷貝和賦值函數(shù)呢?
虛擬函數(shù)使用:C++exams\destructor
在編寫派生類的賦值函數(shù)時(shí),注意不要忘記對(duì)基類的數(shù)據(jù)成員重新賦值。例如:
C++exams\base_operator