1. 程式人生 > >C++:哪些變數會自動初始化?

C++:哪些變數會自動初始化?

C++建構函式動態記憶體靜態變數全域性變數

在C語言中的全域性變數和靜態變數都是會自動初始化為0,堆和棧中的區域性變數不會初始化而擁有不可預測的值。 C++保證了所有物件與物件成員都會初始化,但其中基本資料型別的初始化還得依賴於建構函式。 下文來詳細探討C風格的”預設初始化”行為,以及C++中成員變數的初始化規則。

初始化的語法

很多人至今不知道C++中如何正確地初始化一個變數,我們首先來解決語法的問題。 C語言中在宣告時用=即可完成初始化操作。但我們偏向於使用C++風格(本文中均指面向物件程式設計風格)來初始化內建型別:

// C 風格
int i = 3;
int arr[] = {1, 2, 3};

// C++ 風格
int i(3);
int i = int(3);
int *p = new int(3);
int[] arr = new int[]{1, 2, 3};

在C語言中int a;表示聲明瞭整型a但未初始化,而C++中的物件總是會被初始化的,無論是否寫了圓括號或者是否寫了引數列表,例如:

int basic_var;      // 未初始化:應用"預設初始化"機制
CPerson person;     // 初始化:以空的引數列表呼叫建構函式

預設初始化規則

定義基本資料型別變數(單個值、陣列)的同時可以指定初始值,如果未指定C++會去執行預設初始化(default-initialization)。 那麼什麼是”預設初始化”呢?

  • 棧中的變數(函式體中的自動變數)和堆中的變數(動態記憶體)會保有不確定的值;
  • 全域性變數和靜態變數(包括區域性靜態變數)會初始化為零。

C++11: If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2.

所以函式體中的變數定義是這樣的規則:

int i;                    // 不確定值
int i = int();            // 0
int *p = new int;         // 不確定值
int *p = new int();       // 0

靜態和全域性變數的初始化

未初始化的和初始化為零的靜態/全域性變數編譯器是同樣對待的,把它們儲存在程序的BSS段(這是全零的一段記憶體空間)中。所以它們會被”預設初始化”為零。

來看例子:

int g_var;
int *g_pointer;
static int g_static;

int main(){
    int l_var;
    int *l_pointer;
    static int l_static;

    cout<<g_var<<endl<<g_pointer<<endl<<g_static<<endl;
    cout<<l_var<<endl<<l_pointer<<endl<<l_static<<endl;
};

輸出:

0                   // 全域性變數
0x0                 // 全域性指標  
0                   // 全域性靜態變數
32767               // 區域性變數
0x7fff510cfa68      // 區域性指標
0                   // 區域性靜態變數

動態記憶體中的變數在上述程式碼中沒有給出,它們和區域性變數(自動變數)具有相同的”預設初始化”行為。

成員變數的初始化

成員變數分為成員物件和內建型別成員,其中成員物件總是會被初始化的。而我們要做的就是在建構函式中初始化其中的內建型別成員。 還是先來看看內建型別的成員的”預設初始化”行為:

class A{
public:
    int v;
};
A g_var;

int main(){
    A l_var;
    static A l_static;
    cout<<g_var.v<<' '<<l_var.v<<' '<<l_static.v<<endl;
    return 0;
}

輸出:

0 2407223 0

可見內建型別的成員變數的”預設初始化”行為取決於所在物件的儲存型別,而儲存型別對應的預設初始化規則是不變的。 所以為了避免不確定的初值,通常會在建構函式中初始化所有內建型別的成員。Effective C++: Item 4一文討論瞭如何正確地在建構函式中初始化資料成員。 這裡就不展開了,直接給出一個正確的初始化寫法:

class A{
public:
    int v;
    A(): v(0);
};

封閉類巢狀成員的初始化

再來探討一下當物件聚合發生時成員變數的”預設初始化”行為,同樣還是隻關注於基本資料型別的成員。

class A{
public:
    int v;
};

class B{
public:
    int v;
    A a;
};

B g_var;
int main(){
    B l_var;
    cout<<g_var.v<<' '<<g_var.a.v<<endl;
    cout<<l_var.v<<' '<<l_var.a.v<<endl;
    return 0;
}

輸出:

0 0
43224321 -1610612736

規則還是是一樣的,預設初始化行為取決於它所屬物件的儲存型別。 封閉類(Enclosing)中成員物件的內建型別成員變數的”預設初始化”行為取決於當前封閉類物件的儲存型別,而儲存型別對應的預設初始化規則仍然是不變的