C++類構造函數初始化列表

構造函數初始化列表以一個冒號開始,接着是以逗號分隔的數據成員列表,每個數據成員後面跟一個放在括號中的初始化式。例如:

class CExample {
public
:
   int
a;
   float
b;
   //構造函數初始化列表

    CExample(): a(0),b(8.8
)
   {}

   //構造函數內部賦值
    CExample()
   
{
        a=0
;
        b=8.8
;
    }

}
;

上面的例子中兩個構造函數的結果是一樣的。上面的構造函數(使用初始化列表的構造函數)顯式的初始化類的成員;而沒使用初始化列表的構造函數是對類的成員賦值,並沒有進行顯式的初始化。

初始化和賦值對內置類型的成員沒有什麼大的區別,像上面的任一個構造函數都可以。對非內置類型成員變量,爲了避免兩次構造,推薦使用類構造函數初始化列表。但有的時候必須用帶有初始化列表的構造函數:
1.成員類型是沒有默認構造函數的類。若沒有提供顯示初始化式,則編譯器隱式使用成員類型的默認構造函數,若類沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。
2.const成員或引用類型的成員。因爲const對象或引用類型只能初始化,不能對他們賦值。



初始化數據成員與對數據成員賦值的含義是什麼?有什麼區別?
首先把數據成員按類型分類並分情況說明:
1.內置數據類型,複合類型(指針,引用)
    在成員初始化列表和構造函數體內進行,在性能和結果上都是一樣的
2.用戶定義類型(類類型)
    結果上相同,但是性能上存在很大的差別。因爲類類型的數據成員對象在進入函數體前已經構造完成,也就是說在成員初始化列表處進行構造對象的工作,調用構造函數,在進入函數體之後,進行的是對已經構造好的類對象的賦值,又調用個拷貝賦值操作符才能完成(如果並未提供,則使用編譯器提供的默認按成員賦值行爲)

Note:
初始化列表的成員初始化順序:
    C++初始化類成員時,是按照聲明的順序初始化的,而不是按照出現在初始化列表中的順序。
    Example:

class CMyClass {
    CMyClass(int x,int
y);
   int
m_x;
   int
m_y;
}
;

CMyClass::CMyClass(int x,int
y) : m_y(y), m_x(m_y)
{
}

你可能以爲上面的代碼將會首先做m_y=I,然後做m_x=m_y,最後它們有相同的值。但是編譯器先初始化m_x,然後是m_y,,因爲它們是按這樣的順序聲明的。結果是m_x將有一個不可預測的值。有兩種方法避免它,一個是總是按照你希望它們被初始化的順序聲明成員,第二個是,如果你決定使用初始化列表,總是按照它們聲明的順序羅列這些成員。這將有助於消除混淆。

 

一、   成員初始化列表的位置。

成員初始化列表的位置位於構造函數的函數體和參數表之間。構造函數初始化列表以一個冒號開始,接着是以逗號分隔的數據成員列表,每個數據成員後面跟一個放在括號中的初始化式,初始化式可以是表達式、派生類構造函數的形參還有其餘常量。

通過成員初始化表,類數據成員可以被顯式初始化。成員初始化表是由逗號分隔的成員/名字實參對。例如下面的雙參數構造函數的實現就使用了成員初始化表。

_name是string 型的成員類對象。

Account::Account( const char* name, double opening_bal ): _name( name ), _balance( opening_bal )
{  

_acct_nmbr = get_unique_acct_nmbr();   

}

  
成員初始化表跟在構造函數的原型後,由冒號開頭。成員名是被指定的,後面是括在括號中的初始值,類似於函數調用的語法。如果成員是類對象則初始值變成被傳遞給適當的構造函數的實參。該構造函數然後被應用在成員類對象上。在我們的例子中,name被傳遞給應用在_name上的string構造函數。_balance 用參數opening_bal初始化。

Account::Account( const string& name, double opening_bal ) : _name( name ), _balance( opening_bal )
{    

_acct_nmbr = get_unique_acct_nmbr();   

}

說明:在這種情況下,string 的拷貝構造函數被調用。把成員類對象_name 初始化成string 參數name。


二、   使用初始化表和在構造函數內使用數據成員的賦值之間有什麼區別?

1Account::Account( const char *name, double opening_bal ): _name( name ), _balance( opening_bal )
{     

_acct_nmbr = get_unique_acct_nmbr();   

}

2Account::Account( const char *name, double opening_bal )
{
       _name = name;
       _balance = opening_bal;
       _acct_nmbr = get_unique_acct_nmbr();
}

這兩種實現有區別嗎?

兩種實現的最終結果是一樣的。在兩個構造函數調用的結束處三個成員都含有相同的值。區別是上面的構造函數(使用初始化列表的構造函數)顯式的初始化類的成員;而沒使用初始化列表的構造函數是對類的成員賦值,並沒有進行顯式的初始化。

我們可以認爲構造函數的執行過程被分成兩個階段:隱式或顯式初始化階段以及一般的計算階段。計算階段由構造函數體內的所有語句構成,在計算階段中數據成員的設置被認爲是賦值而不是初始化。沒有清楚地認識到這個區別是程序錯誤和低效的常見源泉。
    初始化階段可以是顯式的或隱式的取決於是否存在成員初始化表。隱式初始化階段按照聲明的順序依次調用所有基類的缺省構造函數然後是所有成員類對象的缺省構造函數。

如:

Account::Account()
{
        _name = "";
        _balance = 0.0;
        _acct_nmbr = 0;
}

則初始化階段是隱式的。在構造函數體被執行之前先調用與_name相關聯的缺省string構造函數。這意味着把空串賦給_name的賦值操作是沒有必要的。對於類對象,在初始化和賦值之間的區別是巨大的。成員類對象應該總是在成員初始化表中被初始化而不是在構造函數體內被賦值。
缺省Account構造函數更正確的實現如下:

Account::Account() : _name( string() )
{
       _balance = 0.0;
       _acct_nmbr = 0;
}

它之所以更正確,是因爲我們已經去掉了在構造函數體內不必要的對_name 的賦值。但是對於缺省構造函數的顯式調用也是不必要的。下面是更緊湊但卻等價的實現:

Account::Account()
{
      _balance = 0.0;
      _acct_nmbr = 0;
}

剩下的問題是:對於兩個被聲明爲內置類型的數據成員其初始化情況如何?例如用成員初始化表和在構造函數體內初始化_balance是否等價?回答是不。對於非類數據成員的初始化或賦值除了兩個例外,兩者在結果和性能上都是等價的。即更受歡迎的實現是用成員初始化表:

// 更受歡迎的初始化風格
Account:: Account(): _balanae( 0.0 ), _acct_nmbr( 0 ) { }

兩個例外是:指任何類型的const 和引用數據成員。const 和引用數據成員也必須是在成員初始化表中被初始化,否則就會產生編譯時刻錯誤。

例如下列構造函數的實現將導致編譯:


class ConstRef
{
public:
       ConstRef( int ii );
private:
       int i;
       const int ci;
       int &ri;
};

ConstRef:: ConstRef( int ii )
{ /
/賦值
      i = ii; // ok
      ci = ii; //
錯誤: 不能給一個 const 賦值
      ri = i; //錯誤:ri 沒有被初始化
}

當構造函數體開始執行時,所有const和引用的初始化必須都已經發生。只有將它們在成員初始化表中指定,這纔有可能。正確的實現如下:

ConstRef::ConstRef( int ii ):ci( ii ), ri( i )
{ i = ii; }

每個成員在成員初始化表中只能出現一次,初始化的順序不是由名字在初始化表中的順序決定而是由成員在類中被聲明的順序決定的。

例:

class Account
{
public:
// ...
private:
    unsigned int _acct_nmbr;
    double _balance;
    string _name;
};

下面是該類的缺省構造函數:

Account::Account(): _name( string() ), _balance( 0.0 ), _acct_nmbr( 0 ) {}

的初始化順序爲acct_nmbr ,_balance 然後是_name 。(由類體內聲明的次序決定的。)但是在初始化表中出現或者在被隱式初始化的成員類對象中的成員,總是在構造函數體內成員的賦值之前被初始化。例如:

Account::Account( const char *name, double bal ) : _name( name ), _balance( bal )
{
       _acct_nmbr = get_unique_acct_nmbr();
}

初始化的順序是_balance , _name 然後是_acct_nmbr。
(爲什麼?因爲_balance 在類體內的聲明在 _name之前,_acct_nmbr的聲明雖然在他們之前,但是因爲他沒有出現在成員初始化表中,所以。。)

由於這種實際的初始化順序與初始化表內的順序之間的明顯不一致有可能導致以下難於發現的錯誤。當用一個類成員初始化另一個時:

class X
{
          int i;
          int j;
public:
                        //
!你看到問題了嗎?
          X( int val ):j( val ), i( j )
              {}
// ...
};

儘管看起來j 好像是用val 初始化的,而且發生在它被用來初始化i 之前,但實際上是i 先被初始化的。因此它是用一個還沒有被初始化的j 初始化的。

我們的建議是把用一個

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章