1)構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù)
構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù)是每個(gè)類最基本的函數(shù)。它們太普通以致讓人容易麻痹大意,
其實(shí)這些貌似簡(jiǎn)單的函數(shù)就象沒有頂蓋的下水道那樣危險(xiǎn)。
每個(gè)類只有一個(gè)析構(gòu)函數(shù)和一個(gè)賦值函數(shù),但可以有多個(gè)構(gòu)造函數(shù)(包含一個(gè)拷貝構(gòu)造函數(shù),其它的稱為普通構(gòu)造函數(shù))。對(duì)于任意一個(gè)類A,如果不想編寫上述函數(shù),C++編譯器將自動(dòng)為A 產(chǎn)生四個(gè)缺省的函數(shù),例如:
A(void); // 缺省的無參數(shù)構(gòu)造函數(shù)
A(const A &a); // 缺省的拷貝構(gòu)造函數(shù)
~A(void); // 缺省的析構(gòu)函數(shù)
A & operate =(const A &a); // 缺省的賦值函數(shù)
這不禁讓人疑惑,既然能自動(dòng)生成函數(shù),為什么還要程序員編寫?原因如下:
<1>如果使用“缺省的無參數(shù)構(gòu)造函數(shù)”和“缺省的析構(gòu)函數(shù)”,等于放棄了自主“初始化”和“清除”的機(jī)會(huì),C++發(fā)明人Stroustrup 的好心好意白費(fèi)了。
<2>“缺省的拷貝構(gòu)造函數(shù)”和“缺省的賦值函數(shù)”均采用“位拷貝”而非“值拷貝”的方式來實(shí)現(xiàn),倘若類中含有指針變量,這兩個(gè)函數(shù)注定將出錯(cuò)。
對(duì)于那些沒有吃夠苦頭的C++程序員,如果他說編寫構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù)很容易,可以不用動(dòng)腦筋,表明他的認(rèn)識(shí)還比較膚淺,水平有待于提高。
下面以類String 的設(shè)計(jì)與實(shí)現(xiàn)為例,深入闡述被很多教科書忽視了的道理。String的結(jié)構(gòu)如下:
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; // 用于保存字符串
};
(2)構(gòu)造函數(shù)是一種特殊的成員函數(shù),無返回值,函數(shù)名與類同名。它提供了對(duì)成員變量進(jìn)行初始化的方法,使得在聲明對(duì)象時(shí)能自動(dòng)地初始化對(duì)象。因?yàn)楫?dāng)程序創(chuàng)建一個(gè)對(duì)象時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用該對(duì)象所屬類的構(gòu)造函數(shù)。
例一:class Student
{
Student()//默認(rèn)無參無賦值操作構(gòu)造函數(shù)
{
}
}
Student stu;//聲明對(duì)象
以上代碼中的無參無操作構(gòu)造函數(shù)即為系統(tǒng)自動(dòng)提供一個(gè)默認(rèn)的構(gòu)造函數(shù),該默認(rèn)構(gòu)造函數(shù)沒有參數(shù),它僅僅負(fù)責(zé)創(chuàng)建對(duì)象而不做任何賦值操作。
例二:class Student
{
Student()//無參帶賦值操作構(gòu)造函數(shù)
{
memberVariable1=constValue1;
memberVariable2=constValue2;
}
}
Student stu;//聲明對(duì)象
以上代碼中,在默認(rèn)構(gòu)造函數(shù)添加賦值初始化操作,該構(gòu)造函數(shù)將覆蓋默認(rèn)構(gòu)造函數(shù)。該構(gòu)造函數(shù)沒有參數(shù),它不僅負(fù)責(zé)創(chuàng)建對(duì)象而還負(fù)責(zé)成員變量的狀態(tài)初始化。
例三:class Student
{
Student(type1 value1,type2 value2) //含參帶賦值操作構(gòu)造函數(shù)
{
memberVariable1=value1;
memberVariable2=value2;
}
}
Student stu(value1,value2);//聲明對(duì)象
以上代碼中,在默認(rèn)構(gòu)造函數(shù)中添加參數(shù)和賦值初始化操作,該構(gòu)造函數(shù)將覆蓋默認(rèn)構(gòu)造函數(shù)。該構(gòu)造函數(shù)沒有參數(shù),它不僅負(fù)責(zé)創(chuàng)建對(duì)象而還負(fù)責(zé)傳值對(duì)成員變量進(jìn)行狀態(tài)初始化。
一旦類中有了一個(gè)帶參數(shù)的構(gòu)造函數(shù)而又沒無參數(shù)構(gòu)造函數(shù)的時(shí)候系統(tǒng)將無法創(chuàng)建不帶參數(shù)的對(duì)象,此時(shí)以下三種聲明都是錯(cuò)誤的:
Student stu;
Student *stu = new Student;
Student *stu = new Student();
例四:class Student
{
Student()
{
}
/*Student()
{
memberVariable1=constValue1;
memberVariable2=constValue2;
}*/
Student(type1 value1,type2 value2)
{
memberVariable1=value1;
memberVariable2=value2;
}
};
Student stu; // 聲明對(duì)象—棧對(duì)象
Student *stu; // 類指針變量—棧對(duì)象
Student *stu = new Student; // çèStudent *stu = new Student();—堆對(duì)象
Student stu(value1,value2); // 聲明對(duì)象—棧對(duì)象
Student *stu = new Student(value1,value2); // 聲明對(duì)象—堆對(duì)象
以上代碼中,既有無參(默認(rèn))構(gòu)造函數(shù),又有含參和賦值操作的構(gòu)造函數(shù);既可聲明無參對(duì)象,也可聲明含參初始化對(duì)象。注意new是在堆上動(dòng)態(tài)創(chuàng)建的。
由于構(gòu)造函數(shù)和普通函數(shù)一樣具有重載特性所以編寫程序的人可以給一個(gè)類添加任意多個(gè)構(gòu)造函數(shù),來使用不同的參數(shù)來進(jìn)行初始化對(duì)象!
類一旦定義就可以當(dāng)作一種新的數(shù)據(jù)類型,可作為另一個(gè)類的數(shù)據(jù)成員,即類可以嵌套定義。
類是一個(gè)抽象的概念,并不是一個(gè)實(shí)體,并不能包含屬性值(這里來說也就是構(gòu)造函數(shù)的參數(shù)了),只有對(duì)象才占有一定的內(nèi)存空間,含有明確的屬性值!
一個(gè)類可能需要在構(gòu)造函數(shù)內(nèi)動(dòng)態(tài)分配資源,那么這些動(dòng)態(tài)開辟的資源就需要在對(duì)象不復(fù)存在之前被銷毀掉,那么c++類的析構(gòu)函數(shù)就提供了這個(gè)方便。
(3)構(gòu)造函數(shù)的初始化表
構(gòu)造函數(shù)有個(gè)特殊的初始化方式叫“初始化表達(dá)式表”(簡(jiǎn)稱初始化表)。初始化表位于函數(shù)參數(shù)表之后,卻在函數(shù)體 {} 之前。這說明該表里的初始化工作發(fā)生在函數(shù)體內(nèi)的任何代碼被執(zhí)行之前。
構(gòu)造函數(shù)初始化表的使用規(guī)則:
<1> 如果類存在繼承關(guān)系,派生類必須在其初始化表里調(diào)用基類的構(gòu)造函數(shù)。例如:
class A
{ …
A(int x); // A 的構(gòu)造函數(shù)
};
class B : public A
{ …
B(int x, int y);// B 的構(gòu)造函數(shù)
};
B::B(int x, int y): A(x) // 在初始化表里調(diào)用A 的構(gòu)造函數(shù)
{ …
}
<2>類的 const 常量只能在初始化表里被初始化,因?yàn)樗荒茉诤瘮?shù)體內(nèi)用賦值的方式來初始化。
<3>類的數(shù)據(jù)成員的初始化可以采用初始化表或函數(shù)體內(nèi)賦值兩種方式,這兩種方式的效率不完全相同。
[1]非內(nèi)部數(shù)據(jù)類型的成員對(duì)象應(yīng)當(dāng)采用第一種方式初始化,以獲取更高的效率。例如:
class A
{ …
A(void); // 無參數(shù)構(gòu)造函數(shù)
A(const A &other); // 拷貝構(gòu)造函數(shù)
A & operate =( const A &other); // 賦值函數(shù)
};
class B
{
public:
B(const A &a); // B 的構(gòu)造函數(shù)
private:
A m_a; // 成員對(duì)象
};
示例 9-2(a)中,類B 的構(gòu)造函數(shù)在其初始化表里調(diào)用了類A的拷貝構(gòu)造函數(shù),從而將成員對(duì)象m_a 初始化。
示例 9-2 (b)中,類B 的構(gòu)造函數(shù)在函數(shù)體內(nèi)用賦值的方式將成員對(duì)象m_a 初始化。我們看到的只是一條賦值語句,但實(shí)際上B 的構(gòu)造函數(shù)干了兩件事:先暗地里創(chuàng)建m_a對(duì)象(調(diào)用了A 的無參數(shù)構(gòu)造函數(shù)),再調(diào)用類A 的賦值函數(shù),將參數(shù)a 賦給m_a。
示例 9-2(a) 成員對(duì)象在初始化表中被初始化:
B::B(const A &a) : m_a(a)
{
…
}
示例9-2(b) 成員對(duì)象在函數(shù)體內(nèi)被初始化:
B::B(const A &a)
{
m_a = a;
…
}
[2]對(duì)于內(nèi)部數(shù)據(jù)類型的數(shù)據(jù)成員而言,兩種初始化方式的效率幾乎沒有區(qū)別,但后者的程序版式似乎更清晰些。若類F的聲明如下:
class F
{
public:
F(int x, int y); // 構(gòu)造函數(shù)
private:
int m_x, m_y;
int m_i, m_j;
}
示例9-2(c)中F 的構(gòu)造函數(shù)采用了第一種初始化方式,示例9-2(d)中F 的構(gòu)造函數(shù)采用了第二種初始化方式。
示例 9-2(c) 數(shù)據(jù)成員在初始化表中被初始化:
F::F(int x, int y) : m_x(x), m_y(y)
{
m_i = 0;
m_j = 0;
}
示例9-2(d) 數(shù)據(jù)成員在函數(shù)體內(nèi)被初始化:
F::F(int x, int y)
{
m_x = x;
m_y = y;
m_i = 0;
m_j = 0;
}
(4)拷貝構(gòu)造函數(shù)和賦值函數(shù)的區(qū)別
拷貝構(gòu)造函數(shù)和賦值函數(shù)非常容易混淆,常導(dǎo)致錯(cuò)寫、錯(cuò)用。拷貝構(gòu)造函數(shù)是在對(duì)象被創(chuàng)建時(shí)調(diào)用的,而賦值函數(shù)只能被已經(jīng)存在了的對(duì)象調(diào)用。以下程序中,第三個(gè)語句和第四個(gè)語句很相似,你分得清楚哪個(gè)調(diào)用了拷貝構(gòu)造函數(shù),哪個(gè)調(diào)用了賦值函數(shù)嗎?
String a(“hello”);
String b(“world”);
String c = a; // 調(diào)用了拷貝構(gòu)造函數(shù),最好寫成c(a);
c = b; // 調(diào)用了賦值函數(shù)
(5)析構(gòu)函數(shù)也是特殊的類成員函數(shù),它沒有返回類型,沒有參數(shù),不能隨意調(diào)用,也沒有重載,只有在類對(duì)象的生命期結(jié)束的時(shí)候,由系統(tǒng)自動(dòng)調(diào)用,用來在系統(tǒng)釋放對(duì)象前做一些清理工作,如利用delete運(yùn)算符釋放臨時(shí)分配的內(nèi)存、清零某些內(nèi)存單元等。
定義析構(gòu)函數(shù)因使用"~"符號(hào)加類名(邏輯非運(yùn)算符),表示它為逆構(gòu)造函數(shù),它不能帶任何參數(shù)。
參考:
《C和指針》
《高質(zhì)量C++編程指南》
《C++拷貝構(gòu)造函數(shù)(深拷貝,淺拷貝)》http://hi.baidu.com/onlinewan/blog/item/df0a8c189879e2b44aedbc73.html