天天看點

C++ 直接初始化與指派初始化,講的很仔細

一、我的問題是關于初始化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會更加自然的出現。有兩種方法避免它,一個是總是按照你希望它們 被初始化的順序聲明成員,第二個是,如果你決定使用初始化清單,總是按照它們聲明的順序羅列這些成員。這将有助于消除混淆。

二、很多的人對中構造函數寝初始化很多的困惑,對冒号後初始化不是太明白,總搞不清楚它們之間的差別,我想把我對這個問題的了解和看法和大家讨論讨 論。 

在程式中定義變量并初始化的機制中,有兩種形式,一個是我們傳統的初始化的形式,即指派運算符指派,還有一種是括号指派,如: 

int a=10; 

char b='r';\\指派運算符指派 

int a(10);\ 

char b('r');\\括号指派 

以上定義并初始化的形式是正确的,可以通過編譯,但括号指派隻能在變量定義并初始化中,不能用在變量定義後再指派,這是和指派運算符指派的不同之處,如: 

(1) 

int a; \\先定義一個變量 

...... 

a=10; \\根據需要指派 

(2) 

int b; \\先定義一個變量 

...... 

b(10); \\和(1)一樣根據需要指派 

(1)是可以用通過編譯,定義一個變量a但并沒有初始化,在需要變量a的時候,通過指派運算符把10賦給a,而在(2)中,是通過括号把10指派給b,但 編譯系統認為 

這是一個函數的調用,函數名為b,10為實際參數,是以編譯錯誤。是以,括号指派隻用在定義變量并初始化中。

現在我們來看構造函數中冒号初始化和函數初始化的問題,類構造函數的作用是建立一個類的對象時,調用它來構造這個類對象的資料成員,一要給出此資料成員分 配記憶體空間,二是要給函數資料成員初始化,構造資料成員是按資料成員在類中聲明的順序進行構造。 

冒号初始化與函數體初始化的差別在于: 

冒号初始化是給資料成員配置設定記憶體空間時就進行初始化,就是說配置設定一個資料成員隻要冒号後有此資料成員的指派表達式(此表達式必須是括号指派表達式),那麼 配置設定了記憶體空間後在進入函數體之前給資料成員指派,就是說初始化這個資料成員此時函數體還未執行。 

對于在函數中初始化,是在所有的資料成員被配置設定記憶體空間後才進行的。 

這樣是有好處的,有的資料成員需要在構造函數調入之後函數體執行之前就進行初始化如引用資料成員,常量資料成員和對象資料成員,看下面的一段程式: 

class student 

{public : 

student () 

protected: 

const int a; 

int &b;

student ::student (int i,int j) 

a=i; 

b=j; 

在Student類中有兩個資料成員,一個是常量資料成員,一個是引用資料成員,并且在構造函數中初始化了這兩個資料成員,但是這并不能通過編譯,因為常 量初始化時必須指派,它的值是不能再改變的,與常量一樣引用初始化也需要指派,定義了引用後,它就和引用的目标維系在了一起,也是不能再被指派的。是以C 

++":"後初始化的機制,使引用和常量資料成員變為可能的,Student類的構造函數應為: 

student ::student(int i,int j):a(i),b(j){} 

在下面的程式: 

class teach 

public : 

teach(char *p="name",int a=0) 

protected: 

char name[30]; 

int age; 

teach ::teach(char*p,int a) 

strcopy(name ,p); 

age=a; 

cout>>name>>endl; 

class student 

public: 

student (char *p="name"); 

protected; 

char name[30]; 

teach teacher; 

}; 

student::student(char *p) 

strcopy(name,p); 

cont>>name>>endl; 

在上面的程式中通不過編譯,編譯系統會告訴你teacher這個類對象缺預設構造函數,因為在teach 類中沒有定義預設的構造函數。那麼帶參數的構造函數怎麼進行構造呢?通過我們前面提到的冒号指派。那它的構造函數應該是: 

student::student(char *p,char *pl,int ag):teacher(pl,ag) 

strcopy(name,p); 

cont>>name>>endl; 

就是說在沒有預設構造函數的時候,如果一個類對象是另一個類的資料成員,那麼初始化這個數 據成員,就應該放到冒号後面。這樣可以帶參數。在類的定義中,如: 

protected; 

char name[30]; 

teach teacher 

類對象是不能帶參數的,因為它隻是聲明。 

是以在C++中就增加了這種機制,這是面向對象程式設計所必須的。不知道我講明白沒有。如不明白請查閱有關資料。

繼續閱讀