在〈定義類別〉中談過,如果沒有定義任何建構式,編譯器會自動產生沒有參數的預設建構式,那麼預設建構式做了什麼呢?如果就以下的類別來說:
class Account {
public:
string id;
string name;
double balance;
};
預設建構式對每個值域進行預設初始化,對基本型態來說,會初始為各型態的零值,就類別型態來說,使用其無參數建構式來初始化,例如 string
來說,會初始為空字串。
如果定義了類別內初始式(in-class initializer),那麼預設建構式會使用初始式,例如:
class Account {
public:
string id = "000-000-000";
string name = "Anonymous";
double balance;
};
id
以定義的初始式初始為 string("000-000-000")
、name
以定義的初始式初始為 string("Anonymous")
,而 double
預設初始為 0.0。
預設建構式只在沒有自定義任何建構式時,編譯器才會產生,若自定義了建構式,就算定義了參數建構式,也不稱為預設建構式。例如:
class Account {
string id;
string name;
double balance;
public:
Account() {
this->id = "000-000-000";
this->name = "Anonymous";
this->balance = 0.0;
};
};
在 C++ 中,絕大多數的情況下,可能不用區分初始化與指定,然而實際上是有分別的,就以上的類別來說,若實例化 Account
,id
、name
、balance
會進行預設初始化,之後執行建構式,將 "000-000-000"
、"Anonymous"
、0.0 指定給對應的值域。
預設建構式只在沒有自定義任何建構式時,編譯器才會產生,建構式可以重載,如果自定義了建構式,也想提供無參建構式,並希望其行為與預設建構式相同,可以加上 default
。例如:
class Account {
string id;
string name;
double balance;
public:
Account() = default;
Account(string id, string name, double balance);
};
在〈定義類別〉中看過 Account
的建構式是這麼定義的:
Account::Account(string id, string name, double balance) {
this->id = id;
this->name = name;
this->balance = balance;
}
如果建構式中想要指定某個值域的值,可以定義初始式清單(constructor initializer list),就上例來說,可以直接在定義類別時撰寫:
class Account {
string id;
string name;
double balance;
public:
Account(string id, string name, double balance) :
id(id), name(name), balance(balance) {};
};
這麼一來,id
值域就會用參數 id
的值初始化,name
值域就會用參數 name
的值初始化,balance
值域就會用參數 balance
的值初始化,括號中指定不一定要是參數,也可以是運算式,如果初始式清單省略了某個值域,那就會使用預設初始化;在這邊,初始式清單的順序並不代表值域初始化的順序,值域初始化的順序是依類別中值域定義的順序而定。
絕大多數的情況下,不區分初始化與指定不會有什麼問題,然而底下不區分的話,就會有問題:
class Foo {
const int wat;
Foo(int wat) {
this->wat = wat;
}
};
若以 Foo(1)
實例化,wat
會預設初始為 0,之後執行建構式流程,然而 wat
被 const
修飾過,不可以在建構式中被指定值了,因此會編譯失敗,然而以下可以通過編譯:
class Foo {
const int wat;
Foo(int wat) : wat(wat) {}
}
如果建構過程,想要委由另一個版本的建構式,可以在 :
後指定。例如:
class Account {
string id;
string name;
double balance;
public:
Account(string id, string name, double balance) :
id(id), name(name), balance(balance) {};
Account(string id, string name) : Account(id, name, 0.0) {}
};
若以 Account acct("123-456-789", "Justin Lin")
建構實例,那麼會先執行 Account(string id, string name, double balance)
的流程,接著才是 Account(string id, string name)
的流程。
在〈定義類別〉中看過,可以使用以下的方式來建構 Account
實例:
Account acct = {"123-456-789", "Justin Lin", 1000};
在〈不定長度引數〉看過,{"123-456-789", "Justin Lin", 1000}
實際上會建立 initializer_list
,可是〈定義類別〉中並沒有定義可接受 initializer_list
的建構式啊?這其實是隱含地型態轉換,預設會尋找符合初始式清單的建構式來進行實例建構。
實際上 string
也是如此,string name = "Justin Lin"
時,"Justin Lin"
是 const *char
型態,隱含地會使用對應的建構式來建構 string
實例。
如果不希望有這種行為,可以在對應的建構式上加上 explicit
,例如〈定義類別〉中的類別若定義為:
class Account {
string id;
string name;
double balance;
public:
explicit Account(string id, string name, double balance);
void deposit(double amount);
void withdraw(double amount);
string to_string();
};
編譯的時候就會看到以下的錯誤訊息:
error: converting to 'Account' from initializer list would use explicit constructor