命名空間
變體
動作

初始化

出自 cppreference.com
< cpp‎ | language
 
 
C++ 語言
一般主題
流程控制
條件執行陳述式
if
疊代陳述式 (迴圈)
for
範圍 for (C++11)
跳躍陳述式
函式
函式宣告
Lambda 函式運算式
inline 指定符
動態例外規範 (直到 C++17*)
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) 列表初始化語法。
expression - 任何表達式(除了未加括號的逗號表達式
運算式列表 - 以逗號分隔的運算式列表(無括號的逗號運算式除外)
初始化器列表 - 以逗號分隔的初始化器子句列表(見下文)
指定初始化器列表 - 以逗號分隔的 指定初始化器子句 列表


「初始化器子句」可以是下列其中之一

expression (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) 「部分有序動態初始化」,適用於所有非隱式或明確實例化特化的內聯(inline)變數。若一個部分有序的 V 在每個編譯單元中都定義於有序或部分有序的 W 之前,則 V 的初始化在 W 的初始化之前(若程式啟動執行緒,則為「發生在...之前」)。
(自 C++17 起)
3) 「有序動態初始化」,適用於所有其他非局部變數:在單一編譯單元內,這些變數的初始化始終定序於其定義在原始碼中出現的確切順序。不同編譯單元中靜態變數的初始化為「未定順序」。不同編譯單元中執行緒局部變數的初始化為「無序」。

若具有靜態或執行緒儲存期的非局部變數初始化因例外而終止,則呼叫 std::terminate

[編輯] 早期動態初始化

若同時滿足下列條件,編譯器允許將動態初始化的變數作為靜態初始化的一部分(本質上是在編譯時期)進行初始化:

1) 動態版本的初始化不會在其初始化之前改變任何其他命名空間範圍物件的值
2) 靜態版本的初始化在被初始化變數中產生的值,與若所有非強制靜態初始化的變數都進行動態初始化時,由動態初始化所產生的值相同。

由於上述規則,若物件 o1 的初始化參照了命名空間範圍的物件 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-use) 之前。若編譯單元中沒有變數或函式被 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 文件 關於 初始化
English Deutsch 日本語 中文(简体) 中文(繁體)