建構函式和初始化表

2021-09-30 08:44:39 字數 3786 閱讀 5496

#include

class account ;

注意:建構函式的初始化列表只在建構函式的定義中指定,而不在宣告中指定

inline account::account( const char* name, double opening_bal )

: _name( name ), _balance( opening_bal )

成員初始化列表跟在建構函式的原型後,以冒號開頭。成員名是被指定的,後面是括在括號中的初始值,類似於函式呼叫的語法。如果成員是類物件,則初始值變成被傳遞給適當的建構函式的實參,該建構函式然後被應用在成員類物件上。在我們的例子中,name被傳遞給應用在_name上的string建構函式,_balance 用引數opening_bal 初始化。類似地,下面是另乙個雙引數account建構函式:

inline account::account( const string& name, double opening_bal )

: _name( name ), _balance( opening_bal )

在這種情況下,string的拷貝建構函式被呼叫,把成員類物件_name 初始化成string 引數name。

c++新手關注的乙個常見問題是,使用初始化列表和在建構函式內使用資料成員的賦值之間有什麼區別。

例如,以下**:

inline account::account( const char *name, double opening_bal )

: _name( name ), _balance( opening_bal )

和inline account::account( const char *name, double opening_bal )

它們的區別是什麼?

兩種實現的最終結果是一樣的。在兩個構造函式呼叫的結束處,三個成員都含有相同的值,區別是成員初始化表只提供該類資料成員的初始化。在建構函式體內對資料成員設定值是乙個賦值操作。區別的重要性取決於資料成員的型別。

在建構函式初始化列表中沒有顯示提及的每個成員,使用與初始化變數相同的規則來進行初始化。執行該型別的預設建構函式,來初始化類型別的資料成員。內建或復合型別的成員的初始值依賴於物件的作用域:在區域性作用域中這些成員不被初始化,而在全域性作用域中,它們被初始化為0(《c++ primer》4th p388)。

在概念上,我們可以認為建構函式的執行過程被分成兩個階段:隱式或顯式初始化階段,以及一般的計算階段:

初始化階段由成員初始化表構成。初始化階段可以是顯式的或隱式的,取決於是否存在成員初始化表。對於類物件來說,隱式初始化階段按照宣告的順序依次呼叫所有基類的預設建構函式,然後是所有成員類物件的預設建構函式。

計算階段由建構函式體內的所有語句構成。在計算階段中,資料成員的設定被認為是賦值,而不是初始化。沒有清楚地認識到這個區別是程式錯誤和低效的常見源泉。

例如,當我們寫如下**:

inline account::account()

則初始化階段是隱式的,在建構函式體被執行之前,先呼叫與_name相關聯的預設string建構函式,這意味著把空串賦給_name的賦值操作是沒有必要的。

對於類物件,在初始化和賦值之間的區別是巨大的。成員類物件應該總是在成員初始化表中被初始化,而不是在建構函式體內被賦值。若類沒有提供賦值運算操作符,你就不能在建構函式裡面對類物件進行賦值了,而在初始化表裡是呼叫類的建構函式來對類物件進行初始化的,和賦值運算子無關。預設account建構函式的更正確的實現如下:

inline account::account() : _name( string() )

它之所以更正確,是因為我們已經去掉了在建構函式體內不必要的對_name的賦值。但是,對於預設建構函式的顯式呼叫也是不必要的,下面是更緊湊但卻等價的實現:

inline account::account()

剩下的問題是,對於兩個被宣告為內建型別的資料成員,其初始化情況如何?例如,用成員初始化表和在建構函式體內初始化_balance 是否等價?回答是不。對於非類資料成員的初始化或賦值,除了兩個例外,兩者在結果和效能上都是等價的。更受歡迎的實現是用成員切始化表:

// 更受歡迎的初始化風格

inline account::account() : _balanae( 0.0 ), _acct_nmbr( 0 )

兩個例外是指任何型別的const 和引用資料成員。const 和引用資料成員也必須是在成員初始化表中被初始化,否則,就會產生編譯時刻錯誤。例如,下列建構函式的實現將導致編譯時刻錯誤:

class constref ;

constref::constref( int ii )

因為const和引用變數只能初始化,不能賦值,而在建構函式體裡面實際上是賦值操作,而在初始化表中才是真的初始化。因此,只有將它們在成員初始化表中指定這才有可能。正確的實現如下:

// ok: 初始化引用和 const

constref::constref( int ii ):ci( ii ), ri( i )

每個成員在成員初始化表中只能出現一次,初始化的順序不是由名字在初始化表中的順序決定,而是由成員在類中被宣告的順序決定的。例如,給出下面的account 資料成員的宣告順序:

class account ;

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 )

初始化的順序是_balance、_name,然後是_acct_nmbr。

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

class x

// ...

};儘管看起來j 好像是用val 初始化的,而且發生在它被用來初始化i之前,但實際上是i先被初始化的,因此它是用乙個還沒有被初始化的j 初始化的。我們的建議是,把「用乙個成員對另乙個成員進行初始化(如果你真的認為有必要)」的**放到建構函式體內。

補充:為了讓你的程式能夠順利編譯,在下面4種情況下,必須使用member initialization list:

n 當初始化乙個reference member時;

n 當初始化乙個const member時;

當類成員中含有乙個const物件時,或者是乙個引用時,他們也必須要通過成員初始化列表進行初始化,因為這兩種物件要在定義後馬上初始化,而在建構函式中,做的是對它們的賦值,這樣是不被允許的。

n 當呼叫乙個base class的constructor,而它擁有一組引數時;

n 當呼叫乙個member class的constructor,而它擁有一組引數時;

n  靜態變數的初始化應該在類外,而且應該在cpp檔案中。

我們知道類的物件的初始化其實就是呼叫它的建構函式完成,如果沒有寫建構函式,編譯器會為你預設生成乙個。如果你自定義了帶引數的建構函式,那麼編譯器將不生成預設建構函式。這樣這個類的物件的初始化必須有引數。如果這樣的類的物件來做另外某個類的成員,那麼為了初始化這個成員,你必須為這個類的物件的建構函式傳遞乙個引數。同樣,如果你在包含它的這個類的建構函式裡用「=」,其實是為這個物件「賦值」而非「初始化」它。所以如果乙個類裡的所有建構函式都是有引數的,那麼這樣的類如果做為別的類的成員變數,你必須顯式的初始化它,你也只能通過成員初始化列表來完成初始化。

建構函式和初始化表

1.無參構造 預設建構函式 無參並非嚴格的沒有引數的建構函式,而是不需要提供實際引數的建構函式,比如存在有預設引數 integer integer int a 10 也算是預設建構函式,可以無參呼叫。integer p1 new integer integer p2 new integer inte...

建構函式初始化列表和初始化函式

其實並沒有所謂的初始化函式的概念,本文中的初始化函式只是說明在函式體內進行賦值。而初始化列表才是真正意義上的物件初始化。使用初始化列表效率會高一點。c 規定,物件的成員變數的初始化動作發生在進入建構函式本體之前。在建構函式體內只是賦值,並不是初始化。請看下面這個栗子 class base publi...

初始化列表和建構函式

const的資料成員和需要用初始化列表,不能用普通的建構函式體內部進行初始化,這稱為常資料成員。const 有常引用,常物件,常資料成員,常成員函式 棧區 記憶體由系統來分配和釋放 堆區 記憶體由程式設計師自己來分配和釋放的 全域性區常量區 區 方法 建構函式與類同名 建構函式沒有返回值 建構函式可...