对象创建时自动执行的初始化代码,以及对象消亡时自动执行的清理动作——让类自己管理好自己的"出生"和"死亡"。
每个对象都有自己的"生命周期"——从创建到销毁。C++ 允许你在这两个时间点上自动执行代码:对象诞生时运行构造函数,对象消亡时运行析构函数。
你不需要手动调用它们,C++ 会在合适的时机自动触发。
})时自动销毁;new 创建的对象在 delete 时销毁。构造函数是一个与类同名、没有返回值的特殊函数。对象创建的那一刻,它自动被调用,负责把成员变量初始化到合法状态。
| 1 | class Student { |
| 2 | public: |
| 3 | string name; |
| 4 | int score; |
| 5 | |
| 6 | Student() { // 构造函数:与类同名,无返回值 |
| 7 | name = "未知"; // 对象创建时自动执行 |
| 8 | score = 0; |
| 9 | } |
| 10 | }; |
| 11 | |
| 12 | int main() { |
| 13 | Student s; // 创建对象,构造函数自动执行 |
| 14 | cout << s.name << endl; // 输出:未知 |
| 15 | cout << s.score << endl; // 输出:0 |
| 16 | } |
构造函数可以有参数,也可以重载——同一个类可以有多个构造函数,C++ 会根据你创建对象时传入的参数自动选择合适的那个。
Student s; 时调用。负责设置"合理的初始状态"。Student s("Alice", 95); 时调用。可以在创建时直接指定初始值。| 1 | class Student { |
| 2 | public: |
| 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 | |
| 15 | int main() { |
| 16 | Student s1; // 调用 ① → name="未知", score=0 |
| 17 | Student s2("Alice", 95); // 调用 ② → name="Alice", score=95 |
| 18 | Student s3 = Student("Bob", 82); // 与 s2 写法等价 |
| 19 | } |
除了在构造函数体内赋值,还有一种更简洁、效率更高的写法——初始化列表,放在函数参数列表后面,用冒号 : 开头。
| 1 | class Student { |
| 2 | public: |
| 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 或引用类型时,只能在初始化列表里设置,不能在函数体内赋值。
| 1 | class Config { |
| 2 | public: |
| 3 | const int MAX_SIZE; // const 成员:一旦初始化不能修改 |
| 4 | |
| 5 | // Config() { MAX_SIZE = 100; } ❌ 编译错误!const 不能赋值 |
| 6 | Config(int n) : MAX_SIZE(n) {} // ✅ 只能在初始化列表里设置 |
| 7 | }; |
析构函数是对象"死亡"时自动执行的函数。它的名字是类名前加波浪号 ~,没有参数,也没有返回值。
析构函数的主要用途是释放资源——比如对象内部用 new 申请了堆内存,就需要在析构函数里 delete 掉,否则会内存泄漏。
| 1 | class Student { |
| 2 | public: |
| 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 | |
| 15 | int main() { |
| 16 | Student s1("Alice", 95); // 输出:Alice 诞生了! |
| 17 | Student s2("Bob", 82); // 输出:Bob 诞生了! |
| 18 | // main 结束,局部对象按创建的反序销毁: |
| 19 | } // 输出:Bob 消亡了。→ Alice 消亡了。 |
s1,再构造 s2;main 结束时先析构 s2,再析构 s1。
new 手动分配内存时才需要在析构函数里 delete。竞赛中通常避免在类里直接 new,所以析构函数往往可以省略——C++ 会自动生成一个什么都不做的默认析构函数。
在 CSP-J / NOIP 中,构造函数最常见的用途是给结构体或类设定默认初始值,让代码更安全、更简洁。
| 1 | // 存图的边:起点、终点、权重 |
| 2 | struct Edge { |
| 3 | int u, v, w; |
| 4 | Edge(int u, int v, int w) : u(u), v(v), w(w) {} |
| 5 | }; |
| 6 | |
| 7 | // 使用时直接传参初始化,更简洁 |
| 8 | vector<Edge> edges; |
| 9 | edges.push_back(Edge(1, 2, 5)); // 直接构造,不用逐字段赋值 |
| 10 | edges.emplace_back(2, 3, 8); // emplace_back 直接原地构造,更高效 |
const 成员必须用它。~类名(),对象销毁时自动执行,竞赛中通常无需手动写。