构造函数与初始化列表
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 18 篇。对象出生的瞬间由构造函数接管:它保证"对象一诞生就处于合法状态",这是 C 的结构体做不到的。本篇主角除了构造函数的几种写法,还有冒号后面的初始化列表——那个比函数体赋值更高效的小细节。
本文要回答的问题
- 构造函数什么时候调用?不写构造函数会怎样?
- 初始化列表是什么?凭什么比函数体赋值高效?
- 默认构造、带参构造、重载之间怎么配合?
一、构造函数:对象一出生就被初始化
class Circle {
double radius_;
public:
Circle() : radius_(1.0) {} // 默认构造
Circle(double r) : radius_(r) {} // 带参构造
};
Circle c1; // 调默认构造,radius = 1.0
Circle c2(5.0); // 调带参构造,radius = 5.0
Circle c3{5.0}; // C++11 起,花括号初始化同样可用构造函数的特点:函数名和类名相同、没有返回类型。它在对象创建时自动执行——你写 Circle c2(5.0) 的瞬间,Circle(double r) 就被调用了。这是 C 的结构体完全没有的机制:C 里 struct Circle c; 之后 c.radius 是垃圾值,用之前必须手动初始化,漏一步就是未定义行为。C++ 用构造函数把这个"必须初始化"变成了编译器强制。
二、不写构造函数会怎样?
编译器会自动生成默认构造函数——什么都不做,成员变量保持不确定状态(int/double 是垃圾值)。所以"不写构造"不等于"自动帮你初始化好"。
class Point {
public:
int x, y; // 不写构造:x、y 是垃圾值
};
Point p; // 合法,但 p.x 是垃圾值两个例外:
- 成员带了类内初始值(
int x = 0;),不写构造也会初始化成这个值; - 成员是有构造函数的类类型(比如
std::string),自动调用它的默认构造。
想杜绝"垃圾值"类,养成给成员写类内初始值或自己写构造函数的习惯。
三、初始化列表:冒号后面的魔法
Circle(double r) : radius_(r) {}
// ^^^^^^^^^^^ 初始化列表冒号后面用 成员(值) 的形式给每个成员赋初值。注意它和函数体 { radius_ = r; } 的本质区别:
Circle(double r) : radius_(r) {} // 直接初始化:一步到位
Circle(double r) { radius_ = r; } // 先默认构造,再赋值:两步对 double 这种内建类型,两种写法差别不大;但成员是类类型(std::string、std::vector)时差别立现——第一种直接构造一次,第二种先默认构造出一个空对象、再整体赋值,多了一次构造 + 一次赋值。能用初始化列表就用初始化列表,这是性能习惯,也是唯一能初始化 const 成员和引用的方式:
class Config {
const int limit_; // const 成员
std::string name_; // 类类型成员
public:
Config(int l, const std::string& n)
: limit_(l), name_(n) {} // 只能在这里初始化 const 成员
};const int limit_ 一旦生成就是定值,不能在函数体里"赋值"(赋值意味着改),只能靠初始化列表"初始化"。这个区别到移动语义篇还会再遇到。
四、构造函数的重载与默认参数
构造函数也能重载(第 11 篇的知识直接用上),也能用默认参数:
class Window {
int w_, h_;
public:
Window() : Window(800, 600) {} // 委托构造(C++11)
Window(int w, int h) : w_(w), h_(h) {}
Window(int w) : w_(w), h_(600) {} // 和上面第 3 个重载并存
};注意第 2 行:委托构造——一个构造函数调另一个构造函数,避免重复初始化逻辑。Window() : Window(800, 600) 意思是"默认构造 = 用 800×600 调另一个构造"。
有坑要躲:默认参数和重载别叠加出歧义(第 12 篇讲过),Window(int w, int h = 600) 和 Window(int w) 同时存在会让 Window(800) 调用不明确。默认参数和重载二选一。
五、和 C 对比
| 对比项 | C | C++ |
|---|---|---|
| 初始化 | 手动,漏了是垃圾值 | 构造函数自动执行 |
| 默认状态 | 无保证 | 有构造就有保证 |
| 合法性校验 | 要写初始化函数 init() | 构造内完成 |
| 忘了初始化 | 编译器最多警告 | 类类型自动构造 |
| 结构体字面量 | {1, 2} 初始化 | 初始化列表(列表初始化) |
C 的 init() 函数有个老毛病:调用方可能忘调。构造函数把这个"忘掉"变成了不可能——对象要么没建出来,要么建出来就是合法状态。
六、与本站主线衔接
- 对象销毁时的对偶机制:析构函数,见 析构函数;
- 拷贝构造、拷贝赋值是构造的兄弟,见 拷贝构造与拷贝赋值;
- 构造/析构的调用时机对性能的影响,以及返回值优化,见高手层拷贝省略与 RVO;
- 对象构造失败时 C++ 的异常机制如何接管,见异常与错误处理。
一句话总结
构造函数在对象诞生的瞬间自动执行,把"必须初始化"从人的自觉变成编译器的强制;初始化列表用"成员(值)"直接构造、比函数体赋值少一次默认构造,还能初始化 const 成员和引用;构造函数支持重载、默认参数和 C++11 的委托构造,但默认参数别和重载叠加出歧义。
上一篇:public / private / protected 下一篇:析构函数