14-9 构造函数介绍
当类类型是聚合体时,我们可以使用聚合初始化来直接初始化该类类型:
struct Foo // Foo is an aggregate
{
int x {};
int y {};
};
int main()
{
Foo foo { 6, 7 }; // uses aggregate initialization
return 0;
}
集合初始化采用成员逐个初始化(成员按定义顺序初始化)。因此在上例中,当 foo 被实例化时,foo.x 被初始化为 6,foo.y 被初始化为 7。
相关内容:
我们在第13.8节——结构体聚合初始化中讨论了聚合的定义及其初始化方式。
然而,一旦我们将任何成员变量设为私有(以隐藏数据),该类类型便不再属于聚合(因为聚合不能包含私有成员)。这意味着我们无法再使用聚合初始化:
class Foo // Foo is not an aggregate (has private members)
{
int m_x {};
int m_y {};
};
int main()
{
Foo foo { 6, 7 }; // compile error: can not use aggregate initialization
return 0;
}
禁止通过聚合初始化来初始化具有私有成员的类类型,其合理性体现在以下几个方面:
聚合初始化需要了解类的实现细节(因为必须知道成员的具体内容及其定义顺序),而隐藏数据成员正是为了刻意规避这种依赖。
若类包含某种不变量,则需依赖用户以保持不变量的方式初始化类。
那么如何初始化具有私有成员变量的类?编译器对前例给出的错误信息提供了线索:“error: no matching constructor for initialization of ‘Foo’”
显然需要匹配构造函数。但这究竟是什么?
构造函数
构造函数constructor是一种特殊成员函数,在非聚合类型的对象创建后会自动调用。
当定义非聚合类型的对象时,编译器会检查是否存在可访问的构造函数,且该构造函数能匹配调用方提供的初始化值(若有)。
若找到匹配的可访问构造函数,则为对象分配内存,随后调用构造函数。
若未找到可访问的匹配构造函数,则会产生编译错误。
关键要点:
许多新手程序员常困惑于构造函数是否创建对象。其实不然——编译器在调用构造函数前已完成对象内存分配,构造函数随后对未初始化的对象进行调用。
但若无法为初始化表达式找到匹配构造函数,编译器将报错。因此构造函数虽不直接创建对象,但缺乏匹配构造函数将阻碍对象生成。
除决定对象生成方式外,构造函数通常承担两项功能:
• 通过成员初始化列表完成成员变量初始化
• 通过构造函数主体中的语句执行其他设置操作,例如初始化值错误检查、文件或数据库打开等
构造函数执行完毕后,该对象即被视为“构造完成”,此时对象应处于一致且可用的状态。
需注意聚合体不允许拥有构造函数——若为聚合体添加构造函数,则该对象将失去聚合体属性。
命名构造函数
与普通成员函数不同,构造函数的命名有特定规则:
- 构造函数必须与类同名(大小写一致)。对于模板类,此名称不包含模板参数。
- 构造函数没有返回类型(甚至不能是 void)。
由于构造函数通常是类接口的一部分,它们通常为 public。
一个基本构造函数示例
让我们在上面的示例中添加一个基本构造函数:
#include <iostream>
class Foo
{
private:
int m_x {};
int m_y {};
public:
Foo(int x, int y) // here's our constructor function that takes two initializers
{
std::cout << "Foo(" << x << ", " << y << ") constructed\n";
}
void print() const
{
std::cout << "Foo(" << m_x << ", " << m_y << ")\n";
}
};
int main()
{
Foo foo{ 6, 7 }; // calls Foo(int, int) constructor
foo.print();
return 0;
}
该程序现在将进行编译并输出结果:

当编译器看到定义 Foo foo{ 6, 7 } 时,它会寻找匹配的 Foo 构造函数,该构造函数需接受两个 int 参数。Foo(int, int) 符合条件,因此编译器将允许该定义。
运行时实例化 foo 时,会为其分配内存,随后调用 Foo(int, int) 构造函数,其中参数 x 初始化为 6,参数 y 初始化为 7。构造函数主体执行后会输出 Foo(6, 7) 已构造完成。
当调用 print() 成员函数时,你会发现成员 m_x 和 m_y 的值均为 0。这是因为虽然调用了 Foo(int, int) 构造函数,但并未实际初始化成员变量。具体实现方法将在下一课中说明。
相关内容:
第 14.15 课--类初始化与复制省略 将探讨使用复制初始化、直接初始化和列表初始化通过构造函数初始化对象的区别。
构造函数参数的隐式转换
在第10.1节——隐式类型转换中,我们提到编译器会在函数调用中对参数进行隐式转换(如有必要),以匹配参数类型不同的函数定义:
void foo(int, int)
{
}
int main()
{
foo('a', true); // will match foo(int, int)
return 0;
}
构造函数也是如此:Foo(int, int)构造函数将匹配任何参数可隐式转换为int的调用:
class Foo
{
public:
Foo(int x, int y)
{
}
};
int main()
{
Foo foo{ 'a', true }; // will match Foo(int, int) constructor
return 0;
}
构造函数不应为 const
构造函数需要能够初始化正在构造的对象——因此,构造函数必须不是 const。
#include <iostream>
class Something
{
private:
int m_x{};
public:
Something() // constructors must be non-const
{
m_x = 5; // okay to modify members in non-const constructor
}
int getX() const { return m_x; } // const
};
int main()
{
const Something s{}; // const object, implicitly invokes (non-const) constructor
std::cout << s.getX(); // prints 5
return 0;
}

通常情况下,非 const 成员函数不能在 const 对象上调用。然而,C++ 标准明确规定(参见 class.ctor.general#5),const 不适用于正在构造的对象,仅在构造函数结束之后才生效。
构造函数与设置器
构造函数旨在实例化时初始化整个对象。设置器则用于为现有对象的单个成员赋值。

浙公网安备 33010602011771号