const成员和引用成员必须用初始化列表,因其定义即初始化;自定义类型无默认构造时同理;父类无默认构造时子类也须显式调用;初始化顺序由声明顺序决定,与列表顺序无关。

const 成员和引用成员必须用初始化列表
编译器不允许在构造函数体内对 const 成员或引用成员赋值,因为它们的语义要求“定义即初始化”。一旦对象开始构造,这些成员就已存在,但尚未被赋予有效值——此时只能靠初始化列表完成这唯一一次初始化机会。
常见错误现象:error: uninitialized const member 或 error: uninitialized reference member;或者更隐蔽地,在函数体内写 ref = x; 或 const_val = 42;,直接报错。
- 引用成员:如
int& ref;,必须绑定到一个已存在的对象,不能“先声明再绑定” -
const成员:如const std::string name;,不可修改,所以不能走“默认构造 + 后续赋值”路径 - 二者都不支持默认构造,因此跳过初始化列表就会导致编译失败
自定义类型成员没有默认构造函数时,初始化列表是唯一选择
如果类成员是另一个类的对象,而那个类只提供了带参构造函数(比如 Date(int y, int m, int d)),那么它就没有隐式生成的默认构造函数。此时若在构造函数体内用 = 赋值,编译器会先尝试调用默认构造函数创建该成员,再执行赋值——但默认构造函数不存在,编译直接失败。
使用场景:封装日期、坐标、配置对象等常用结构体/类时,常主动禁用默认构造以避免无效状态。
- 错误写法:
Student s; // 编译失败:Date 没有默认构造函数 - 正确写法:
Student(int y, int m, int d) : birthday(y, m, d) {} - 父类无默认构造时,子类也必须在初始化列表中显式调用父类构造函数,如
: Base(x)
效率差异集中在类类型成员上,基础类型无区别
对 int、double、char* 等内置类型,初始化列表和函数体内赋值生成的汇编几乎一致,性能无差别。但对自定义类型(如 std::string、std::vector、用户定义类),区别明显:
- 初始化列表:直接调用目标类型的带参构造函数,一次完成
- 函数体内赋值:先调用默认构造函数(可能分配内存、初始化内部结构),再调用赋值运算符(可能再次分配、拷贝、释放)
- 典型开销:字符串重复分配缓冲区、容器反复 resize、智能指针多次计数更新
例如 std::string s = "hello"; 在初始化列表中是构造,在函数体内是“默认构造 + operator=”,多一次内存操作。
初始化顺序由声明顺序决定,和初始化列表顺序无关
这是最容易被忽略、也最易引发未定义行为的点。哪怕你在初始化列表里写成 : b(a), a(1),只要类中声明顺序是 int a; int b;,那 a 就一定先于 b 初始化——此时 b(a) 读取的是未初始化的 a,结果不可预测。
常见错误现象:程序偶发崩溃、变量值为随机大数、ASan 报 use-of-uninitialized-value。
- 永远按类内声明顺序书写初始化列表,避免误导自己
- 编译器通常不警告这种顺序错位,需靠代码审查或静态分析工具(如 clang-tidy 的
cppcoreguidelines-pro-type-member-init)捕获 - 如果必须依赖前序成员值,只能把逻辑移到构造函数体内(但要确保该成员已可安全访问)
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











