成員初始化順序表

C++
有如下幾條:
1構造函數初始化列表的變量優先於構造函數(至少明顯的寫在前面) (若都在初始化列表中初始化,則按聲明順序初始化,與初始化列表中的順序無關)
2靜態成員變量先於實例變量
3父類成員變量先於子類成員變量
4父類構造函數先於子類構造函數
java和C#語言
1 類成員變量初始化先於類的構造函數
2 靜態成員變量先於實例變量
3 父類成員變量先於子類成員變量 (C#相反)
4 父類構造函數先於子類構造函數
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
認識成員初始化列表(轉載)
一、 成員初始化列表的位置。
  成員初始化列表的位置位於構造函數的函數體和參數表之間。
  通過成員初始化表,類數據成員可以被顯式初始化。成員初始化表是由逗號分隔的成員/名字實參對。例如下面的雙參數構造函數的實現就使用了成員初始化表。_name是string 型的成員類對象。


 inline 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 初始化。
  說明: 在這種情況下,string 的拷貝構造函數被調用。把成員類對象_name 初始化成string 參數name.

二、 使用初始化表和在構造函數內使用數據成員的賦值之間有什麼區別?
  
inline Account::Account( const char *name, double opening_bal ): _name( name ), _balance( opening_bal )
{
_acct_nmbr = get_unique_acct_nmbr();
}
inline Account::Account( const char *name, double opening_bal )
{
_name = name;
_balance = opening_bal;
_acct_nmbr = get_unique_acct_nmbr();
}
  這兩種實現有區別嗎?兩種實現的最終結果是一樣的。在兩個構造函數調用的結束處三個成員都含有相同的值。區別是成員初始化表只提供該類數據成員的初始化。在構造函數體內對數據成員設置值是一個賦值操作。我們可以認爲構造函數的執行過程被分成兩個階段:隱式或顯式初始化階段以及一般的計算階段。計算階段由構造函數體內的所有語句構成,在計算階段中數據成員的設置被認爲是賦值而不是初始化。沒有清楚地認識到這個區別是程序錯誤和低效的常見源泉.
初始化階段可以是顯式的或隱式的取決於是否存在成員初始化表。隱式初始化階段按照聲明的順序依次調用所有基類的缺省構造函數然後是所有成員類對象的缺省構造函數。
 inline Account::Account()
 {
  _name = "";
  _balance = 0.0;
  _acct_nmbr = 0;
 }
則初始化階段是隱式的。在構造函數體被執行之前先調用與_name 相關聯的缺省string構造函數。這意味着把空串賦給_name 的賦值操作是沒有必要的。對於類對象,在初始化和賦值之間的區別是巨大的。成員類對象應該總是在成員初始化表中被初始化而不是在構造函數體內被賦值。
缺省Account 構造函數的更正確的實現如下:
inline Account::Account() : _name( string() )
{
_balance = 0.0;
_acct_nmbr = 0;
}

它之所以更正確,是因爲我們已經去掉了在構造函數體內不必要的對_name 的賦值。但是對於缺省構造函數的顯式調用也是不必要的。下面是更緊湊但卻等價的實現:
inline Account::Account()
{
_balance = 0.0;
_acct_nmbr = 0;
} 剩下的問題是:對於兩個被聲明爲內置類型的數據成員其初始化情況如何?例如用成員初始化表和在構造函數體內初始化_balance 是否等價?回答是不。對於非類數據成員的
初始化或賦值除了兩個例外,兩者在結果和性能上都是等價的。即更受歡迎的實現是用成員切始化表:
// 更受歡迎的初始化風格
inline 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 和引用的初始化必須都已經發生。只有將它們在成員初始化表中指定,這纔有可能。正確的實現如下:
// ok: 初始化引用和 const
ConstRef::ConstRef( int ii ):ci( ii ), ri( i )
{ i = ii; }
每個成員在成員初始化表中只能出現一次,初始化的順序不是由名字在初始化表中的順序決定而是由成員在類中被聲明的順序決定的。例:
class Account
{
public:
// ...
private:
unsigned int _acct_nmbr;
double _balance;
string _name;
};
下面是該類的缺省構造函數:
inline Account::Account(): _name( string() ), _balance( 0.0 ), _acct_nmbr( 0 )
{}
的初始化順序爲acct_nmbr , _balance 然後是_name 。(由類體內聲明的次序決定的。)但是在初始化表中出現或者在被隱式初始化的成員類對象中的成員,總是在構造函數體內成員的賦值之前被初始化。例如:
inline 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 初始化的。我們的建議是把用一個成員對另一個成員進行初始化的代碼放到構造函數體內。X::X( int val ) : i( val ) { j = i; }

---------------------------------------------------------------------------------------------------------------------
何謂C++初始化列表
  與其他函數不同,構造函數除了有名字,參數列表和函數體之外,還可以有初始化列表,初始化列表以冒號開頭,後跟一系列以逗號分隔的初始化字段。
  struct foo
  {
  string name ;int id ;
  foo(string s, int i):name(s), id(i){} ; // 初始化列表
  };
構造函數的兩個執行階段
  從概念上來講,構造函數的執行可以分成兩個階段,初始化階段和計算階段,初始化階段先於計算階段
初始化階段
  所有類類型(class type)的成員都會在初始化階段初始化,即使該成員沒有出現在構造函數的初始化列表中
計算階段
  一般用於執行構造函數體內的賦值操作。
  下面的代碼定義兩個結構體,其中Test1有構造函數,拷貝構造函數及賦值運算符,爲的是方便查看結果,Test2是個測試類,它以Test1的對象爲成員,我們看一下Test2的構造函數是怎麼樣執行的。
  class Test1
  {
  Test1() // 無參構造函數
  {cout << "Construct Test1" << endl ;}
  Test1(const Test1& t1) // 拷貝構造函數
  {cout << "Copy constructor for Test1" << endl ;this->a = t1.a ;}
  Test1& operator = (const Test1& t1) // 賦值運算符
  {cout << "assignment for Test1" << endl ;this->a = t1.a ;return *this;}
  int a ;
  };
  struct Test2
  {
  Test1 test1 ;
  Test2(Test1 &t1)
  {test1 = t1 ;}
  };
  調用代碼:
  Test1 t1 ;
  Test2 t2(t1) ;
  輸出:
  Construct Test1
  Construct Test1
  assignment for Test1
  解釋一下:
  第一行輸出對應調用代碼中第一行,構造一個Test1對象
  第二行輸出對應Test2構造函數中的代碼,用默認的構造函數初始化對象test1 // 這就是所謂的初始化階段
  第三行輸出對應Test2的賦值運算符,對test1執行賦值操作 // 這就是所謂的計算階段
爲什麼使用初始化列表
  初始化類的成員有兩種方式,一是使用初始化列表,二是在構造函數體內進行賦值操作。
  主要是性能問題,對於內置類型,如int, float等,使用初始化類表和在構造函數體內初始化差別不是很大,但是對於類類型來說,最好使用初始化列表,爲什麼呢?由下面的測試可知,使用初始化列表少了一次調用默認構造函數的過程,這對於數據密集型的類來說,是非常高效的。同樣看上面的例子,我們使用初始化列表來實現Test2的構造函數。
  struct Test2
  {
  Test1 test1 ;
  Test2(Test1 &t1):test1(t1){}
  }
  使用同樣的調用代碼,輸出結果如下:
  Construct Test1
  Copy constructor for Test1
  第一行輸出對應 調用代碼的第一行
  第二行輸出對應Test2的初始化列表,直接調用拷貝構造函數初始化test1,省去了調用默認構造函數的過程。
  所以一個好的原則是,能使用初始化列表的時候儘量使用初始化列表
哪些東西必須放在初始化列表中
  除了性能問題之外,有些時場合初始化列表是不可或缺的,以下幾種情況時必須使用初始化列表
  1. 常量成員,因爲常量只能初始化不能賦值,所以必須放在初始化列表裏面
  2. 引用類型,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裏面
  3. 沒有默認構造函數的類類型,因爲使用初始化列表可以不必調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化
  struct Test1
  {
  Test1(int a):i(a){}
  int i;
  };
  struct Test2
  {
  Test1 test1 ;
  Test2(Test1 &t1)
  {test1 = t1;}
  };
  以上代碼無法通過編譯,因爲Test2的構造函數中test1 = t1這一行實際上分成兩步執行:
  1. 調用Test1的默認構造函數來初始化test1
  2. 調用Test1的賦值運算符給test1賦值
  但是由於Test1沒有默認的構造函數,所謂第一步無法執行,故而編譯錯誤。正確的代碼如下,使用初始化列表代替賦值操作
  struct Test2
  {
  Test1 test1 ;
  Test2(Test1 &t1):test1(t1){}
  }
成員變量的初始化順序
  成員是按照他們在類中出現的順序進行初始化的,而不是按照他們在初始化列表出現的順序初始化的,看代碼:
  struct foo
  {
  int i ;int j ;
  foo(int x):i(x), j(i){}; // ok, 先初始化i,後初始化j
  };
  再看下面的代碼:
  struct foo
  {
  int i ;int j ;
  foo(int x):j(i), i(x){} // i值未定義
  };
  這裏i的值是未定義的因爲雖然j在初始化列表裏面出現在i前面,但是i先於j定義,所以先初始化i,而i由j初始化,此時j尚未初始化,所以導致i的值未定義。一個好的習慣是,按照成員定義的順序進行初始化。

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