这在 8.5 Initializers [dcl.init] 中处理。
零初始化
5/ 零初始化T 类型的对象或引用意味着:
——如果T是标量类型(3.9),则对象设置为值0(零),作为整数常量表达式,转换为T。如 4.10 中所述,将值为 0 的整型常量表达式转换为指针类型会导致空指针
价值。
—如果T 是(可能是 cv 限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都初始化为零,填充初始化为零位;
—如果T 是(可能是 cv 限定的)联合类型,则对象的第一个非静态命名数据成员被零初始化,填充被初始化为零位;
——如果T是一个数组类型,每个元素都是零初始化的;
——如果T是引用类型,则不执行初始化。
基本上,它等价于memset(&obj, 0, sizeof(objt));,只是它说明空指针的内存表示可能不是 0 值(即使它在语言中用 0 表示)。
// foo.cpp
static char const* p; // p is zero-initialized
// during static initialization
static void init() {
if (!p) { p = new char[500]; } // fine as p has been 0-initialized
}
注意:我个人还是更喜欢用= nullptr来初始化p,只是为了说明意图……
默认初始化
6/ 默认初始化T 类型的对象意味着:
——如果T 是一个(可能是cv 限定的)类类型(第9 条),则调用T 的默认构造函数(如果T 没有可访问的默认构造函数,则初始化是错误的) ;
——如果T是一个数组类型,每个元素都是默认初始化的;
——否则,不执行初始化。
如果程序调用 const 限定类型 T 的对象的默认初始化,T 应是具有用户提供的默认构造函数的类类型。
或者基本上,调用默认构造函数,考虑数组,至少对于类。最后一点是对内置函数的警告(例如int)。那些只是保持原样(里面有垃圾)。
默认初始化是当你定义一个变量但没有显式初始化它时调用的。初始化列表中未列出的类的属性也会发生这种情况。因此,内置函数的警告对程序员来说非常重要。
int function() {
int a; // <-- a is default-initialized (which means nothing happens...)
return a; // <-- uses a, so technically undefined behavior
}
struct A { int a; A() {} }; // During the call to A::A(),
// A::a is default-initialized (nothing happens...)
没有显式初始化是 C 语言的遗留问题。通常出于优化原因,但如果尝试使用该值会导致未定义行为...
值初始化
7/ 对T 类型的对象进行值初始化意味着:
— 如果T 是(可能是 cv 限定的)类类型(第 9 条),带有用户提供的构造函数 (12.1),则调用 T 的默认构造函数(并且初始化格式错误如果T 没有可访问的默认构造函数);
——如果T 是一个(可能是 cv 限定的)非联合类类型,没有用户提供的构造函数,那么对象是零初始化的,如果 T 的隐式声明的默认构造函数是非-trivial,调用该构造函数。
——如果T是一个数组类型,那么每个元素都是值初始化的;
——否则,对象被零初始化。
值初始化的对象被认为是已构造的,因此受本国际标准适用于“构造的”对象、“构造函数已完成的对象”等的规定的约束,即使没有调用构造函数用于对象的初始化。
它是上述两者的混合,意味着以下语法:
template <typename T> T value() { return T(); }
^~~
提供T 的适当初始化实例,无论T 是类类型还是内置类型。模板化代码能够拥有如此统一的语法非常重要。
请注意,在 C++11 中,也可以使用 T{} 来实现相同的效果(这有助于消除函数的歧义)。