← 目录 / 第十二章 · 类与面向对象 / 12.4 构造与析构函数

12.4 构造与析构函数

对象创建时自动执行的初始化代码,以及对象消亡时自动执行的清理动作——让类自己管理好自己的"出生"和"死亡"。

本页目录
12.4.1 对象的生命周期

每个对象都有自己的"生命周期"——从创建到销毁。C++ 允许你在这两个时间点上自动执行代码:对象诞生时运行构造函数,对象消亡时运行析构函数

你不需要手动调用它们,C++ 会在合适的时机自动触发。

🐣
构造函数自动执行
成员变量初始化
使用中
对象生存期
调用成员函数
读写成员变量
💀
析构函数自动执行
释放资源、做清理
局部对象在离开作用域(})时自动销毁;new 创建的对象在 delete 时销毁。
构造和析构都是自动触发的,不需要也不能手动调用析构函数。
12.4.2 构造函数

构造函数是一个与类同名、没有返回值的特殊函数。对象创建的那一刻,它自动被调用,负责把成员变量初始化到合法状态。

C++ · 最简单的构造函数
1class Student {
2public:
3 string name;
4 int score;
5
6 Student() { // 构造函数:与类同名,无返回值
7 name = "未知"; // 对象创建时自动执行
8 score = 0;
9 }
10};
11
12int main() {
13 Student s; // 创建对象,构造函数自动执行
14 cout << s.name << endl; // 输出:未知
15 cout << s.score << endl; // 输出:0
16}
💡
没有构造函数会怎样?如果你不写构造函数,C++ 会自动生成一个默认构造函数——它什么也不做,成员变量的值是随机的垃圾值。这是新手经常遇到的 bug 来源,养成写构造函数的习惯可以避免很多麻烦。
12.4.3 构造函数的三种形式

构造函数可以有参数,也可以重载——同一个类可以有多个构造函数,C++ 会根据你创建对象时传入的参数自动选择合适的那个。

默认构造函数
不需要参数,直接写 Student s; 时调用。负责设置"合理的初始状态"。
带参构造函数
接受参数,写 Student s("Alice", 95); 时调用。可以在创建时直接指定初始值。
委托 / 重载
同一个类可以有多个构造函数,C++ 根据参数类型和数量自动匹配,叫做构造函数重载
C++ · 多个构造函数重载
1class Student {
2public:
3 string name;
4 int score;
5
6 Student() { // ① 默认构造:无参数
7 name = "未知"; score = 0;
8 }
9
10 Student(string n, int s) { // ② 带参构造:指定初始值
11 name = n; score = s;
12 }
13};
14
15int main() {
16 Student s1; // 调用 ① → name="未知", score=0
17 Student s2("Alice", 95); // 调用 ② → name="Alice", score=95
18 Student s3 = Student("Bob", 82); // 与 s2 写法等价
19}
12.4.4 初始化列表

除了在构造函数体内赋值,还有一种更简洁、效率更高的写法——初始化列表,放在函数参数列表后面,用冒号 : 开头。

C++ · 初始化列表写法
1class Student {
2public:
3 string name;
4 int score;
5
6 // 函数体内赋值(常规写法)
7 Student(string n, int s) {
8 name = n; score = s; // 先默认初始化,再赋值
9 }
10
11 // 初始化列表写法(推荐)
12 Student(string n, int s) : name(n), score(s) {}
13 // ↑ 冒号后直接初始化,{}体内为空
💡
为什么推荐初始化列表?
函数体内赋值其实分两步:先用默认方式初始化成员,再赋新值。初始化列表则是直接用目标值初始化,省去了多余的一步,对复杂类型(如 string)效率更高。在竞赛中两者效果相同,但养成写初始化列表的习惯是好的。

初始化列表还有一个必须用的场景:当成员变量是 const 或引用类型时,只能在初始化列表里设置,不能在函数体内赋值。

C++ · const 成员必须用初始化列表
1class Config {
2public:
3 const int MAX_SIZE; // const 成员:一旦初始化不能修改
4
5 // Config() { MAX_SIZE = 100; } ❌ 编译错误!const 不能赋值
6 Config(int n) : MAX_SIZE(n) {} // ✅ 只能在初始化列表里设置
7};
12.4.5 析构函数

析构函数是对象"死亡"时自动执行的函数。它的名字是类名前加波浪号 ~,没有参数,也没有返回值。

析构函数的主要用途是释放资源——比如对象内部用 new 申请了堆内存,就需要在析构函数里 delete 掉,否则会内存泄漏。

C++ · 构造函数 + 析构函数完整示例
1class Student {
2public:
3 string name;
4 int score;
5
6 Student(string n, int s) : name(n), score(s) {
7 cout << name << " 诞生了!" << endl;
8 }
9
10 ~Student() { // 析构函数:~ + 类名,无参数无返回值
11 cout << name << " 消亡了。" << endl;
12 }
13};
14
15int main() {
16 Student s1("Alice", 95); // 输出:Alice 诞生了!
17 Student s2("Bob", 82); // 输出:Bob 诞生了!
18 // main 结束,局部对象按创建的反序销毁:
19} // 输出:Bob 消亡了。→ Alice 消亡了。
时间 → s1 (Alice) s2 (Bob) 构造 s1 构造 s2 析构 s2(先) 析构 s1(后) main() 结束
析构顺序与构造顺序相反——后创建的对象先销毁(栈式结构)。
先构造 s1,再构造 s2main 结束时先析构 s2,再析构 s1
📌
竞赛中需要写析构函数吗?大多数时候不需要。只有当类里面用了 new 手动分配内存时才需要在析构函数里 delete。竞赛中通常避免在类里直接 new,所以析构函数往往可以省略——C++ 会自动生成一个什么都不做的默认析构函数。
12.4.6 竞赛中的实用技巧

在 CSP-J / NOIP 中,构造函数最常见的用途是给结构体或类设定默认初始值,让代码更安全、更简洁。

C++ · 竞赛常用:带默认值的结构体构造函数
1// 存图的边:起点、终点、权重
2struct Edge {
3 int u, v, w;
4 Edge(int u, int v, int w) : u(u), v(v), w(w) {}
5};
6
7// 使用时直接传参初始化,更简洁
8vector<Edge> edges;
9edges.push_back(Edge(1, 2, 5)); // 直接构造,不用逐字段赋值
10edges.emplace_back(2, 3, 8); // emplace_back 直接原地构造,更高效
📖
本节小结
构造函数:与类同名,无返回值,对象创建时自动执行,负责初始化。
初始化列表:冒号后直接初始化成员,比函数体内赋值更简洁高效,const 成员必须用它。
析构函数~类名(),对象销毁时自动执行,竞赛中通常无需手动写。
・析构顺序与构造顺序相反