C++ 进阶 — 第 6 课
Student s; s.name = "Alice"; —— 分两步初始化,又啰嗦又容易忘。之前你写 s.name = "Alice"; s.score = 95; —— 又啰嗦又容易忘。构造函数让你一行搞定:
class Student {
public:
string name;
int score;
Student(string n, int s) { // 构造函数:名字跟类名一样,没有返回值
name = n;
score = s;
}
};
Student s1("Alice", 95); // 创建时直接传参
Student s2 = {"Bob", 87}; // 初始化列表写法,等效
cout << s1.name; // Alice
第 ③ 点最值钱:你根本没写「调用」这两个字,编译器在你声明对象的那一刻就帮你调了。所以只要构造函数写对,就不存在「忘了初始化」这种 bug。
Student(string n, int s) : name(n), score(s) {
// 构造体可以为空
}
name(n), score(s) 叫初始化列表,对比:
| 赋值写法 | 初始化列表 |
|---|---|
name = n; score = s; | : name(n), score(s) |
| 先默认初始化,再赋值(两步) | 直接初始化(一步,更高效) |
两种写法结果一样,但初始化列表是「一步到位」:成员直接用地道的方式造出来,而不是先造一个空的再改。成员一多,这个差别就是实打实的开销。
class Student {
public:
string name;
int* scores; // 动态分配的数组
Student(string n, int cnt) : name(n) {
scores = new int[cnt]; // 构造时 new
}
~Student() { // 析构函数:~类名,无参数无返回值
delete[] scores; // 销毁时自动 delete
cout << name << " 被销毁了" << endl;
}
};
{
Student s("Alice", 5); // 构造
} // 离开作用域 → 自动调用析构 → delete[] scores
注意最后两行:} 一出现,s 就离开了作用域,析构函数立刻被调用,不需要你写任何一行「清理」代码。
new 了什么,析构函数里就 delete 什么。这叫「资源获取即初始化」 —— C++ 最重要的编程哲学。
不写构造函数时,编译器自动生成一个什么都不做的默认构造函数。但你一旦写了带参数的构造函数,默认的就没了:
class Student {
public:
string name;
Student(string n) : name(n) {} // 写了这个
};
Student s1; // ❌ 报错!没提供参数
Student s2("Alice"); // ✅
想两种都用,自己加一个无参版本:
Student() {} // 默认构造函数
Student(string n) : name(n) {} // 带参构造函数
这两个版本可以同时存在,因为它们参数不同 —— 这叫构造函数重载。析构函数就不行,它只能有一个。
class Timer {
int seconds;
public:
Timer() : seconds(0) {} // 默认构造
Timer(int s) : seconds(s) {} // 带参构造
~Timer() { cout << "计时器销毁" << endl; } // 析构
void tick() { seconds++; }
int get() { return seconds; }
};
int main() {
Timer t(60); // 调用带参构造,倒计时 60 秒
t.tick(); // 61
cout << t.get();
} // t 离开作用域 → 自动析构
输出是 61 然后是 计时器销毁。这两行的顺序就是对象的完整一生:构造(0)→ 干活(61)→ 析构。
void 都不写。手滑写了 void Student(...),它就成了普通成员函数,对象再也不会自动初始化。Student s; 直接编译错误,要用就自己补一个 Student() {}。delete[],漏掉就是内存泄漏,而且程序看着还挺正常。: score(s), name(n) 不会错,但读代码的人和编译器看的都是成员的声明顺序,按声明写最不容易看错。inc() 方法 +1dec() 方法 -1get() 返回当前值加练一道:把 Counter 再加一个「名字」字段,用初始化列表初始化它;然后创建两个 Counter,一个在 main 里,一个在花括号 { } 里,看析构打印的先后顺序 —— 你能亲眼看到「离开作用域」这件事。
void 或者返回类型?new 在堆上了。inc() / dec() 改的是成员变量,还是不小心改成了局部变量?main 一起贴过来。: 成员(值), 成员(值) 比先默认构造再赋值更高效。~类名(),销毁时自动调用,负责清理。ofstream 写文件、ifstream 读文件、getline 读整行,以及「文件打不开怎么办」。