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)中成員物件的內建型別成員變數的”預設初始化”行為取決於當前封閉類物件的儲存型別,而儲存型別對應的預設初始化規則仍然是不變的。