1. 我們可以給建構函式的參數提供一個預設的實參。
class Account {
public:
// 預設建構函式
Account();
// 聲明中的參數名不是必需的
Account( const char*, double=0.0 );
const char* name() { return _name; }
// ...
private:
// ...
};
下面是兩個合法的 Account 類對象定義,它們向建構函式傳遞了一個或兩個實參:
int main()
{
// ok: 都調用雙參數建構函式
Account acct( "Ethan Stern" );
Account *pact = new Account( "Michael Lieberman", 5000 );
if ( strcmp( acct.name(), pact->name() ))
// ...
}
2. 建構函式形式
// 推薦的建構函式形式
Account acct1( "Anna Press" );
C++語言新手常犯的錯誤是:按如下方式聲明一個用預設建構函式初始化的對象
// 喔! 並沒有像期望的那樣工作
Account newAccount();
它能通過編譯 但是 當我們試圖使用它時
// 編譯錯誤
if ( !newAccount.name() ) ...
編譯器會抱怨我們不能把成員訪問符應用到函數上,定義
// 定義了一個函數 newAccount,
// 不是一個 Account 類對象
Account newAccount();
被編譯器解釋為定義了一個沒有參數,返回一個 Account 類型對象的函數——完全不是我們的意圖,用預設建構函式初始化類對象的正確聲明是去掉尾部的小括弧。
// ok: 定義了一個類對象
Account newAccount;
只有當沒有建構函式或聲明了預設建構函式時,我們才能不指定實參集來定義類對象。 如果定義了帶參數的建構函式,最好也定義一個預設的建構函式:
// 預設 Account 建構函式
inline Account::
Account() {
_name = 0;
_balance = 0.0;
_acct_nmbr = 0;
}
3. 成員初始化表
類的初始化有一種可替換的文法,成員初始化表(member initialization list ),是由逗號分開的成員名及其初值的列表。例如。預設的Account 建構函式可以這樣寫:
// 使用成員初始化表的預設 Account 建構函式
inline Account::Account()
: _name( 0 ),
_balance( 0.0 ), _acct_nmbr( 0 )
{}
成員初始化表只能在建構函式定義中被指定,而不是在其聲明中 。該初始化表被放在參數表和建構函式體之間,由冒號開始,下面是雙參數的建構函式,部分利用了成員初始化表:
inline Account::
Account( const char* name, double opening_bal )
: _balance( opening_bal )
{
_name = new char[ strlen(name)+1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
4. 建構函式用作轉換操作符
考慮如下程式段 :
// 在某個標頭檔中
extern void print( const Account &acct );
// ...
int main()
{
// 把 "oops" 轉換成一個 Account 對象
// 用 Account::Account( "oops", 0.0 )
print( "oops" );
// ...
}
預設情況下,單參數建構函式或者建構函式有多個參數(除了第一個參數外, 其他都有預設實參 )被用作轉換操作符,在上面的程式段中的print()的調用裡 Account 建構函式被編譯器隱式地應用,以便把一個文字字串轉換成一個 Account 對象,儘管這種轉換在這種情況下並不合適。
無意的隱式類轉換,如把 oops 轉換成一個 Account 對象,已經被證明是很難跟蹤的錯誤源,關鍵字 explicit 被引入到標準 C++中 以協助我們抑制這種不受歡迎的編譯器輔助行為 ,explicit 修飾符通知編譯器不要提供隱式轉換:
class Account{
public:
explicit Account( const char*, double=0.0 );
// ...
};
explicit 只能被應用在建構函式上。
5. 拷貝建構函式
用一個類對象初始化該類的另一個對象被稱為預設按成員初始化(default memberwise initialization ),在概念上 ,一個類對象向該類的另一個對象作拷貝是通過依次拷貝每個非待用資料成員來實現的。
預設按成員的初始化對於類的正確行為常常是不合適的, 通過定義拷貝建構函式的顯式執行個體,我們可以改變預設的行為,我們的Account 類要求我們這樣做, 否則兩個Account 對象會有相同的帳號,這在該類的規範中顯然是不允許的。
拷貝建構函式有一個指向類對象的引用作為形式參數,傳統上被聲明為 const ,下面是它的實現:
inline Account::
Account( const Accout &rhs )
: _balance( rhs._balance )
{
_name = new char[ strlen(rhs._name)+1 ];
strcpy( _name, rhs._name );
// 不能拷貝 rhs._acct_nmbr
_acct_nmbr = get_unique_acct_nmbr();
}
當我們寫
Account acct2( acct1 );
編譯器判斷是否為 Account 類聲明了一個顯式的拷貝建構函式,如果聲明了拷貝建構函式,並且是可以訪問的 ,則調用它;如果聲明了拷貝建構函式但是不可訪問,則acct2的定義就是一個編譯時間刻錯誤; 如果沒有聲明拷貝建構函式的執行個體,則執行預設的按成員初始化。如果我們後來引入或去掉了一個拷貝建構函式的聲明,則使用者程式無需改變,但是,需要重新編譯它們 。