我的問題是關於初始化C++類成員的。我見過許多這樣的代碼(包括在你的欄目中也見到過):
CSomeClass::CSomeClass()
{
x=0;
y=1;
}
而在別的什麼地方則寫成下面的樣子:
CSomeClass::CSomeClass() : x(0), y(1)
{
}
我的一些程序員朋友說第二種方法比較好,但他們都不知道爲什麼是這樣。你能告訴我這兩種類成員初始化方法的區別嗎?
回答
從技術上說,你的程序員朋友是對的,但是在大多數情況下,兩者實際上沒有區別。有兩個原因使得我們選擇第二種語法,它被稱爲成員初始化列表:一個原因是必須的,另一個只是出於效率考慮。
讓我們先看一下第一個原因——必要性。設想你有一個類成員,它本身是一個類或者結構,而且只有一個帶一個參數的構造函數。
class CMember {
public:
CMember(int x) { ... }
};
因爲Cmember有一個顯式聲明的構造函數,編譯器不產生一個缺省構造函數(不帶參數),所以沒有一個整數就無法創建Cmember的一個實例。
CMember* pm = new CMember; // Error!!
CMember* pm = new CMember(2); // OK
如果Cmember是另一個類的成員,你怎樣初始化它呢?你必須使用成員初始化列表。
class CMyClass {
CMember m_member;
public:
CMyClass();
};
//必須使用成員初始化列表
CMyClass::CMyClass() : m_member(2)
{
•••
}
沒有其它辦法將參數傳遞給m_member,如果成員是一個常量對象或者引用也是一樣。根據C++的規則,常量對象和引用不能被賦值,它們只能被初始化。
第二個原因是出於效率考慮,當成員類具有一個缺省的構造函數和一個賦值操作符時。MFC的Cstring提供了一個完美的例子。假定你有一個類CmyClass具有一個Cstring類型的成員m_str,你想把它初始化爲"yada yada."。你有兩種選擇:
CMyClass::CMyClass() {
// 使用賦值操作符
// CString::operator=(LPCTSTR);
m_str = _T("yada yada");
}
//使用類成員列表
// and constructor CString::CString(LPCTSTR)
CMyClass::CMyClass() : m_str(_T("yada yada"))
{
}
在 它們之間有什麼不同嗎?是的。編譯器總是確保所有成員對象在構造函數體執行之前初始化,因此在第一個例子中編譯的代碼將調用CString:: Cstring來初始化m_str,這在控制到達賦值語句前完成。在第二個例子中編譯器產生一個對CString:: CString(LPCTSTR)的調用並將"yada yada"傳遞給這個函數。結果是在第一個例子中調用了兩個Cstring函數(構造函數和賦值操作符),而在第二個例子中只調用了一個函數。在 Cstring的例子裏這是無所謂的,因爲缺省構造函數是內聯的,Cstring只是在需要時爲字符串分配內存(即,當你實際賦值時)。但是,一般而言, 重複的函數調用是浪費資源的,尤其是當構造函數和賦值操作符分配內存的時候。在一些大的類裏面,你可能擁有一個構造函數和一個賦值操作符都要調用同一個負 責分配大量內存空間的Init函數。在這種情況下,你必須使用初始化列表,以避免不要的分配兩次內存。在內部類型如ints或者longs或者其它沒有構 造函數的類型下,在初始化列表和在構造函數體內賦值這兩種方法沒有性能上的差別。不管用那一種方法,都只會有一次賦值發生。有些程序員說你應該總是用初始 化列表以保持良好習慣,但我從沒有發現根據需要在這兩種方法之間轉換有什麼困難。在編程風格上,我傾向於在主體中使用賦值,因爲有更多的空間用來格式化和 添加註釋,你可以寫出這樣的語句:x=y=z=0;
或者memset(this,0,sizeof(this));
注意第二個片斷絕對是非面向對象的。
當我考慮初始化列表的問題時,有一個奇怪的特性我應該警告你,它是關於C++初始化類成員的,它們是按照聲明的順序初始化的,而不是按照出現在初始化列表中的順序。
class CMyClass
{
CMyClass(int x, int y);
int m_x;
int m_y;
};
CMyClass::CMyClass(int i) : m_y(i), m_x(m_y)
{
}
你 可能以爲上面的代碼將會首先做m_y=I,然後做m_x=m_y,最後它們有相同的值。但是編譯器先初始化m_x,然後是m_y,,因爲它們是按這樣的順 序聲明的。結果是m_x將有一個不可預測的值。我的例子設計來說明這一點,然而這種bug會更加自然的出現。有兩種方法避免它,一個是總是按照你希望它們 被初始化的順序聲明成員,第二個是,如果你決定使用初始化列表,總是按照它們聲明的順序羅列這些成員。這將有助於消除混淆。
--------------------------------------------------------
原則上用初始化列表來進行成員的初始化.
除非你覺得不爽(很沒原則的傢伙).
經過實驗,其實初始化表同樣可以做很複雜的初始化.
--------------------------------------------------------
命題爲僞命題,根據C++標準,初始化只有1種方法:初始化列表。
另外一種是默認初始化+賦值,也許有相同的效果,卻是完全不同的概念。
具體去看C++ Primer 3e 14.1章。
--------------------------------------------------------
1 是成員初始化列表
2 是賦值
第二種方式其實是 第一中方式的基礎上, 對這些成員再次進行了賦值。
所以,
採用第一種方法是比較好的,
高效。
不過, 對於內置數據類型,
兩者差不多 ~
--------------------------------------------------------
我認爲比較好的類初始化機制是構造函數,它保證在每個對象的首次使用之前被編譯器自動應用在每個類對象上