作用
变量的初始化在构造时提供初值
初始化器
- 初值可以由声明符或 new 表达式的初始化器部分提供。
- 对于每个声明符,初始化器必须是下列之一:
( 表达式列表 )
= 表达式
{ 初始化器列表 }
根据其上下文,初始化器可以调用:
- 值初始化,例如
std::string s{};
- 直接初始化,例如
std::string s("hello");
- 复制初始化,例如
std::string s = "hello";
- 列表初始化,例如
std::string s{'a', 'b', 'c'};
- 聚合初始化,例如
char a[3] = {'a', 'b'};
- 引用初始化,例如
char& c = a[0];
若不提供初始化器,则应用默认初始化的规则。
非局部变量
- 所有具有静态存储期的非局部变量,作为程序启动的一部分,在mian函数的执行之前进行初始化(除非被延迟,见下文)。
- 所有具有线程局部存储期的非局部变量,作为线程启动的一部分进行初始化,按顺序早于线程函数的执行开始
对于上面两种变量,初始化发生于两个截然不同的阶段:
静态初始化
- 如果被允许,则首先进行常量初始化(参见常量初始化中符合情况的列表)
- 对于所有其他非局部静态以及线程局域变量,均进行零初始化
实践中:
- 常量初始化通常在编译期进行,并将预先计算的对象表示作为程序映像的一部分存储下来。如果编译器没有这么做,则亦保证此初始化发生早于任何动态初始化
- 零初始化的变量将被置于程序映像的
.bss
段,它不占用磁盘空间,并在加载程序时由操作系统以零填充
动态初始化
在所有静态初始化完成之后,在下列情形中进行非局部变量的动态初始化:
- 无序的动态初始化,仅适用于未被显式特化的(静态/线程局域)类模板静态数据成员及变量模板 (C++14 起)。这些静态变量的初始化相对于所有其他动态初始化之间是顺序不确定的,但若程序在初始化某个变量之前开始了一个线程,该情况下初始化则是无顺序的 (C++17 起)。这些线程局域变量的初始化相对于所有其他动态初始化之间是无顺序的。
- 部分有序的动态初始化,适用于并未被隐式或显式实例化的特化的所有 inline 变量。若部分有序的 V 在每个翻译单元中比有序或部分有序的 W 更早定义,则 V 的初始化按顺序早于(或若程序启动了线程,则为先发生于)W 的初始化。
- 有序的动态初始化,适用于所有其他非局部变量:在单个翻译单元中,这些变量的初始化始终严格以其定义出现于源代码中的顺序定序。不同翻译单元中的静态变量的初始化之间是顺序不确定的。不同翻译单元中的线程局域变量的初始化之间是无顺序的。
当拥有静态或线程存储期的非局部变量的初始化通过异常退出时,调用 std::terminate。
早期动态初始化
若下列条件皆为真,则允许编译器作为静态初始化(实为编译期)的一部分初始化动态初始化的变量:
- 初始化的动态版本不改变命名空间作用域中任何先于其初始化的对象的值
- 初始化的静态版本在被初始化变量中产生的值,与当所有不要求静态初始化的变量都被动态初始化时,由动态初始化所生成的值相同。
因为上述规则,若某对象 o1 的初始化涉及到命名空间作用域对象 o2,而它潜在地要求动态初始化,但在同一翻译单元中在其之后定义,则所用的 o2 是完全初始化的 o2 的值(因为编译器把 o2 的初始化提升到编译时)还是 o2 仅被零初始化的值,是未指明的。
inline double fd() { return 1.0; }
extern double d1;
double d2 = d1; // 未指明:
// 若 d1 被动态初始化则动态初始化为 0.0,或
// 若 d1 被静态初始化则动态初始化为 1.0,或
// 静态初始化为 0.0(因为当两个变量都被动态初始化时将为这个值)
double d1 = fd(); // 可能静态或动态初始化为 1.0
初始化联合和结构
- 如果联合没有构造函数,你可以使用单个值(或者联合的另一个实例)对其初始化。该值用于初始化第一个非静态字段。
- 结构初始化和联合初始化不同,其初始值设置项中第一个值用于初始化第一个字段,第二个值用于初始化第二个字段,依此类推
我们来看个例子:
struct MyStruct {
int myInt;
char myChar;
};
union MyUnion {
int my_int;
char my_char;
bool my_bool;
MyStruct my_struct;
};
int main() {
MyUnion mu1{ 'a' }; // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
MyUnion mu2{ 1 }; // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
MyUnion mu3{}; // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
MyUnion mu4 = mu3; // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
//MyUnion mu5{ 1, 'a', true }; // compiler error: C2078: too many initializers
//MyUnion mu6 = 'a'; // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
//MyUnion mu7 = 1; // compiler error: C2440: cannot convert from 'int' to 'MyUnion'
MyStruct ms1{ 'a' }; // myInt = 97, myChar = '\0'
MyStruct ms2{ 1 }; // myInt = 1, myChar = '\0'
MyStruct ms3{}; // myInt = 0, myChar = '\0'
MyStruct ms4{1, 'a'}; // myInt = 1, myChar = 'a'
MyStruct ms5 = { 2, 'b' }; // myInt = 2, myChar = 'b'
}
自动初始化和静态初始化析构函数被调用的时机
// initialization_of_objects.cpp
// compile with: /EHsc
#include <iostream>
#include <string.h>
using namespace std;
// Define a class that logs initializations and destructions.
class InitDemo {
public:
InitDemo( const char *szWhat );
~InitDemo();
private:
char *szObjName;
size_t sizeofObjName;
};
// Constructor for class InitDemo
InitDemo::InitDemo( const char *szWhat ) :
szObjName(NULL), sizeofObjName(0) {
if ( szWhat != 0 && strlen( szWhat ) > 0 ) {
// Allocate storage for szObjName, then copy
// initializer szWhat into szObjName, using
// secured CRT functions.
sizeofObjName = strlen( szWhat ) + 1;
szObjName = new char[ sizeofObjName ];
strcpy_s( szObjName, sizeofObjName, szWhat );
cout << "Initializing: " << szObjName << "\n";
}
else {
szObjName = 0;
}
}
// Destructor for InitDemo
InitDemo::~InitDemo() {
if( szObjName != 0 ) {
cout << "Destroying: " << szObjName << "\n";
delete szObjName;
}
}
// Enter main function
int main() {
InitDemo I1( "Auto I1" ); {
cout << "In block.\n";
InitDemo I2( "Auto I2" );
static InitDemo I3( "Static I3" );
}
cout << "Exited block.\n";
}
- 首先,当控制流推出在其中定义
I1
和I2
的块时,二者将自动被销毁 - 第二,在 C++ 中,没有必要在块的开始处声明对象或变量。 此外,只有当控制流到达其定义时,才会初始化这些对象。 ( I2 和 I3 是此类定义的示例。)输出显示初始化的确切时间。
- 最后,静态局部变量(比如I3)在程序持续时间内保留其值,但在持续终止时将被销毁