(三) 构造函数、初始化列表与析构函数
上一节先创建对象再调用设置方法;现在希望创建时就准备好数据,并在离开作用域时完成清理。沿一个对象从创建到销毁观察这些操作。
构造函数在对象创建时初始化它;析构函数在对象生命周期结束时执行清理。
- 构造函数与类同名,没有返回类型,可以重载。
- 析构函数写为
~类名(),没有返回类型和参数。 explicit可以阻止构造函数参与某些隐式转换。- 成员初始化列表写在函数体之前,如
: name_(name), score_(score)。
注意: 成员初始化顺序取决于它们在类中的声明顺序,不取决于初始化列表的书写顺序。
const成员和引用成员通常需要在初始化阶段给出值。
初始化与赋值发生在不同阶段。 初始化列表直接决定成员如何创建;在构造函数体里写赋值时,成员已经完成初始化,再执行赋值。例如 string 成员在进入函数体前就已经是一个对象,所以优先在初始化列表中提供初值。引用在初始化列表中绑定到对象,const 成员也在这里获得初值;进入函数体时,它们已经完成创建。
下面按执行顺序观察:创建 first 时输出“构造 A”;进入内层作用域创建 second,输出“构造 B”;遇到内层右花括号先销毁 second,输出“析构 B”;之后才执行“离开内层作用域”;main 结束时销毁 first。同一作用域中的普通局部对象按完成构造的相反顺序销毁,这让资源能随作用域自动释放。
// 构造与析构打印对象名;内层 B 先离开作用域,因此先析构 B。
#include <iostream>
#include <string>
using namespace std;
class Trace {
private:
string name_;
public:
explicit Trace(string name) : name_(name) {
cout << "构造 " << name_ << '\n';
}
~Trace() {
cout << "析构 " << name_ << '\n';
}
};
int main() {
Trace first("A");
{
Trace second("B");
}
cout << "离开内层作用域\n";
}输出:
构造 A
构造 B
析构 B
离开内层作用域
析构 A
普通局部对象在离开作用域时销毁;使用 new 创建的对象需要由相应所有者负责释放。