命名空间
变体
操作

初始化

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++11 中弃用*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

变量的初始化 在构造时为其提供初始值。

初始值可以在 声明符new 表达式 的初始化器部分中提供。它也发生在函数调用期间:函数参数和函数返回值也会被初始化。

内容

[编辑] 初始化器

对于每个声明符,初始化器(如果存在)可以是以下之一

= 表达式 (1)
= {}
= { 初始化器列表 }
= { 指定初始化器列表 }
(2)

(自 C++20 起)
( 表达式列表 )
( 初始化器列表 )
(3) (在 C++11 之前)
(自 C++11 起)
{}
{ 初始化器列表 }
{ 指定初始化器列表 }
(4) (自 C++11 起)
(自 C++11 起)
(自 C++20 起)
1) 复制初始化语法。
2) 聚合初始化语法。(在 C++11 之前)列表初始化语法。(自 C++11 起)
3) 直接初始化语法。
4) 列表初始化语法。
表达式 - 任何表达式(除了未加括号的 逗号表达式
表达式列表 - 逗号分隔的表达式列表(除了未加括号的逗号表达式)
初始化器列表 - 逗号分隔的初始化子句列表(见下文)
指定初始化器列表 - 逗号分隔的 指定初始化器子句 列表


初始化器子句 可以是以下之一

表达式 (1)
{} (2)
{ 初始化器列表 } (3)
{ 指定初始化器列表 } (4) (自 C++20 起)

语法 (2-4) 统称为花括号括起来的初始化器列表

[编辑] 初始化器语义

如果未为对象指定初始化器,则该对象为默认初始化。如果未为引用指定初始化器,则程序是非良构的。

如果为对象指定的初始化器是 ()(由于语法限制,不能出现在声明符中),则该对象为值初始化。如果为引用指定的初始化器是 (),则程序是非良构的。

初始化器的语义如下

  • 如果正在初始化的实体是引用,请参阅引用初始化
  • 否则,正在初始化的实体是一个对象。给定对象的类型为 T
  • 如果初始化器的语法为 (2)
(在 C++11 之前)
  • 如果初始化器的语法为 (2)(4),则该对象为列表初始化
(自 C++11 起)
#include <string>
 
std::string s1;           // default-initialization
std::string s2();         // NOT an initialization!
                          // actually declares a function “s2”
                          // with no parameter and returns std::string
std::string s3 = "hello"; // copy-initialization
std::string s4("hello");  // direct-initialization
std::string s5{'a'};      // list-initialization (since C++11)
 
char a[3] = {'a', 'b'}; // aggregate initialization
                        // (part of list initialization since C++11)
char& c = a[0];         // reference initialization

[编辑] 非局部变量

所有具有静态存储期的非局部变量都在程序启动时初始化,在main 函数的执行开始之前(除非延迟,见下文)。所有具有线程局部存储期的非局部变量都在线程启动时初始化,在线程函数的执行开始之前排序。对于这两类变量,初始化发生在两个不同的阶段

[编辑] 静态初始化

静态初始化有两种形式

1) 如果可能,应用常量初始化
2) 否则,非局部静态和线程局部变量被零初始化

在实践中

  • 常量初始化通常在编译时应用。预先计算的对象表示形式作为程序映像的一部分存储。如果编译器没有这样做,它仍然必须保证初始化发生在任何动态初始化之前。
  • 要进行零初始化的变量放置在程序映像的 .bss 段中,该段不占用磁盘空间,并在操作系统加载程序时清零。

[编辑] 动态初始化

在所有静态初始化完成后,非局部变量的动态初始化在以下情况下发生

1) 无序动态初始化,仅适用于(静态/线程局部)类模板静态数据成员 变量模板(自 C++14 起),它们不是显式特化的。此类静态变量的初始化与所有其他动态初始化不定序 ,除非程序在变量初始化之前启动线程,在这种情况下,其初始化是不定序的(自 C++17 起)。此类线程局部变量的初始化与所有其他动态初始化不定序。
2) 部分有序动态初始化,适用于所有内联变量,这些变量不是隐式或显式实例化的特化。如果在每个翻译单元中,部分有序的 V 在有序或部分有序的 W 之前定义,则 V 的初始化在 W 的初始化之前排序(或者先发生,如果程序启动线程)。
(自 C++17 起)
3) 有序动态初始化,适用于所有其他非局部变量:在单个翻译单元中,这些变量的初始化始终按照它们在源代码中定义的确切顺序排序。不同翻译单元中静态变量的初始化是不定序的。不同翻译单元中线程局部变量的初始化是不定序的。

如果具有静态或线程存储期的非局部变量的初始化通过异常退出,则调用 std::terminate

[编辑] 早期动态初始化

如果以下条件都为真,则编译器允许将动态初始化的变量作为静态初始化的一部分(本质上是在编译时)进行初始化

1) 初始化的动态版本不会在其初始化之前更改任何命名空间作用域对象的的值
2) 如果所有不需要静态初始化的变量都动态初始化,则初始化的静态版本在初始化的变量中产生的值与动态初始化产生的值相同。

由于上述规则,如果某个对象 o1 的初始化引用了一个命名空间作用域对象 o2,而 o2 可能需要动态初始化,但定义在同一翻译单元的后面,则不确定使用的 o2 的值将是完全初始化的 o2 的值(因为编译器将 o2 的初始化提升到编译时),还是仅仅是零初始化的 o2 的值。

inline double fd() { return 1.0; }
 
extern double d1;
 
double d2 = d1;   // unspecified:
                  // dynamically initialized to 0.0 if d1 is dynamically initialized, or
                  // dynamically initialized to 1.0 if d1 is statically initialized, or
                  // statically initialized to 0.0 (because that would be its value
                  // if both variables were dynamically initialized)
 
double d1 = fd(); // may be initialized statically or dynamically to 1.0

[编辑] 延迟动态初始化

动态初始化是在 main 函数(对于静态变量)或线程的初始函数(对于线程局部变量)的第一个语句之前发生,还是延迟到之后发生,这是实现定义的。

如果非内联变量(自 C++17 起)的初始化被延迟到 main/线程函数的第一个语句之后发生,则它发生在与要初始化的变量在同一翻译单元中定义的任何具有静态/线程存储期的变量的第一个ODR 使用之前。如果给定翻译单元中没有变量或函数被 ODR 使用,则在该翻译单元中定义的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。但是,只要翻译单元中的任何内容被 ODR 使用,即使程序中未使用所有初始化或销毁具有副作用的非局部变量,它们也会被初始化。

如果内联变量的初始化被延迟,则它发生在对该特定变量的第一个ODR 使用之前。

(自 C++17 起)
// ============
// == File 1 ==
 
#include "a.h"
#include "b.h"
 
B b;
A::A() { b.Use(); }
 
// ============
// == File 2 ==
 
#include "a.h"
 
A a;
 
// ============
// == File 3 ==
 
#include "a.h"
#include "b.h"
 
extern A a;
extern B b;
 
int main()
{
    a.Use();
    b.Use();
}
 
// If a is initialized before main is entered, b may still be uninitialized
// at the point where A::A() uses it (because dynamic initialization is
// indeterminately sequenced across translation units)
 
// If a is initialized at some point after the first statement of main (which odr-uses
// a function defined in File 1, forcing its dynamic initialization to run),
// then b will be initialized prior to its use in A::A

[编辑] 静态局部变量

有关局部(即块作用域)静态和线程局部变量的初始化,请参阅静态块变量

初始化器在具有外部或内部链接的变量的块作用域声明中是不允许的。此类声明必须带有 extern,并且不能是定义。

[编辑] 类成员

非静态数据成员可以使用成员初始化列表默认成员初始化器进行初始化。

[编辑] 注意

非局部变量的销毁顺序在 std::exit 中描述。

[编辑] 缺陷报告

以下行为更改缺陷报告被追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
CWG 270 C++98 静态数据成员的初始化顺序
类模板的初始化顺序未指定
指定为无序,除了
显式特化和定义
CWG 441 C++98 具有静态存储期的非局部引用
并非总是在动态初始化之前初始化
被视为静态初始化,始终
在动态初始化之前初始化
CWG 1415 C++98 块作用域 extern 变量
声明可以是定义
禁止(没有初始化器
在此类声明中允许)
CWG 2599 C++98 不清楚是否评估函数
初始化器中的参数是初始化的一部分
它是初始化的一部分

[编辑] 参见

C 文档 用于 初始化