成员变量的默认初始化
本节阅读量:当定义结构体(或类)类型时,可以为每个成员提供默认初始化值,作为类型定义的一部分。该过程称为非静态成员初始化,初始化值称为默认成员初始值设定项。
下面是一个示例:
|
|
在上面的Something定义中,x没有默认值,y使用值初始化,z具有默认值2。如果用户在实例化Something类型的对象时没有提供显式初始化值,则将使用这些默认值。
s1对象没有初始值设定项,因此s1的成员会被初始化为各自的默认值。s1.x没有默认成员初始值设定项,因此它保持未初始化状态。s1.y使用值初始化,因此获得值0。s1.z用值2初始化。
请注意,尽管我们没有为s1.z提供显式初始值设定项,但它被初始化为非零值,因为提供了默认值。
对于高级读者
CTAD(类模板参数推导–后续介绍)不能用于非静态成员初始化。
显式初始化值优先于默认值
列表初始化中的显式值始终优先于成员变量的默认初始化值。
|
|
在上述情况下,s2的每个成员变量都有显式初始化值,因此根本不会使用默认成员初始值。这意味着s2.x、s2.y和s2.z分别初始化为值5、6和7。
存在默认值时,初始化列表中可以缺少初始化值
在上一课中,我们注意到,如果使用聚合初始化,但初始化值的数量小于成员变量的数量,则所有剩余的成员都将被值初始化。然而,如果为给定成员提供了默认初始值,则将改用该默认值初始化。
|
|
在上面的例子中,s3使用空列表初始化,因此缺少所有初始值设定项。这意味着如果存在默认成员初始值设定项,就会使用它;否则将进行值初始化。因此,s3.x(没有默认初始值)初始化为0,s3.y值初始化为0,而s3.z使用默认值2。
重新审视初始化
如果使用初始化列表设置聚合类型的对象:
- 如果存在显式初始化值,则使用该显式值。
- 如果缺少初始值设定项,并且存在默认成员初始值设定项,则使用默认值。
- 如果缺少初始值设定项,并且不存在默认成员初始值设定项,则会进行值初始化。
如果未使用初始化列表:
- 如果存在默认成员初始值设定项,则使用默认值。
- 如果不存在默认的成员初始值设定项,则该成员将保持未初始化状态。
成员始终按声明的顺序初始化。
下面的示例概括了所有可能性:
|
|
要注意的情况是s1.x。由于s1没有初始值设定项列表,并且x没有默认的成员初始值,因此s1.x保持未初始化状态(这不符合最佳实践,因为应该始终初始化变量)。
始终为成员提供默认值
为了避免成员未初始化,只需确保每个成员都有一个默认值(显式默认值或空的大括号对)。这样,无论是否提供初始值设定项列表,成员都会使用某个值进行初始化。
考虑以下结构体,它为所有成员都设置了默认值:
|
|
这里在所有情况下,成员变量都会被初始化。
最佳实践
为所有成员提供默认值。这确保即使变量定义未使用初始值列表,也会初始化成员。
聚合的默认初始化与值初始化
回顾上述示例中的两行:
|
|
您会注意到,f1是默认初始化的,f2是值初始化的,但结果是相同的(numerator初始化为0,denominator初始化为1)。那么应该选择哪一个呢?
值初始化(f2)更安全,因为它将确保任何没有默认值的成员都被值初始化。
首选值初始化还有一个好处——它与初始化其他类型对象的方式一致。一致有助于防止错误。
也就是说,程序员对类类型使用默认初始化而不是值初始化也比较常见。部分是由于历史原因(因为值初始化直到C++11才引入),部分是因为在某些情况下(对于非聚合),默认初始化可能效率更高(后面会在构造函数中讲解)。
因此,在本教程中,不会强制要求对结构体和类使用值初始化,但建议这样做。
最佳实践
对于聚合类型,首选值初始化(有空大括号),而不是默认初始化(没有大括号)。