建構函數主體內賦值(m_name = name;)屬於「先預設建構、再指派」,對於 const 成員、參考成員、無預設建構函數的類別成員,**根本無法編譯**。參數初始化串列直接在物件記憶體佈局完成時「一次到位」初始化,語法:
ClassName::ClassName(params) : member1(val1), member2(val2) { /* 體 */ }
class Student {
const char* m_name;
int m_age;
float m_score;
public:
Student(const char* name, int age, float score)
: m_name(name), m_age(age), m_score(score) { } // 體可為空
void show() const;
};
等價於主體內三行指派,但更高效(避免預設建構+指派),且支援 const/參考成員。
class Demo {
int m_a;
int m_b;
public:
Demo(int b) : m_b(b), m_a(m_b) { } // 串列寫 m_b 在前
void show() const { cout << m_a << ", " << m_b << endl; }
};
int main() {
Demo d(100);
d.show(); // 輸出:垃圾值, 100
}
鐵律:成員初始化順序**嚴格遵循類別內宣告順序**(m_a 先於 m_b),與初始化串列書寫順序無關。上例中 m_a(m_b) 讀取尚未初始化的 m_b,導致 m_a 獲得不定值。
最佳實踐:初始化串列書寫順序與宣告順序保持一致,並開啟編譯器警告(-Wreorder)。
class VLA {
const int m_len; // const 成員
int& m_ref; // 參考成員
NoDefault m_obj; // 無預設建構函數的類別成員
public:
VLA(int len, int& r, const NoDefault& obj)
: m_len(len), m_ref(r), m_obj(obj) { } // 唯一合法初始化路徑
};
若在建構函數主體指派,編譯器將報錯:「未初始化的 const/參考成員」或「找不到預設建構函數」。
Student::Student(const char* name, int age, float score)
: m_name(name) { // 僅 m_name 用串列
m_age = age; // 其餘在主體指派
m_score = score;
}
允許混用,但建議統一風格:能用初始化串列盡量用,效率與一致性較佳。
class Widget {
int m_x;
double m_y;
public:
Widget(int x, double y) : m_x{x}, m_y{y} { } // 大括號初始化
};
大括號初始化禁止隱性窄化轉換(double→int、int→char 可能截斷),編譯器將報錯,增強型別安全。
class Student {
const char* m_name;
int m_age;
float m_score;
public:
Student() : Student("匿名", 0, 0.0f) { } // 委派給三參數版本
Student(const char* name) : Student(name, 0, 0.0f) { }
Student(const char* name, int age, float score)
: m_name(name), m_age(age), m_score(score) { }
};
避免重複初始化邏輯,減少維護負擔。
const、參考、無預設建構成員的合法方式。養成「建構函數首選初始化串列」習慣,是高效、安全 C++ 程式碼的基石。