爲什麼要初始化成員
對於類成員是基礎數據類型,例如int
、char
這些,構造對象時,成員不會被初始化,值是隨機的。下面代碼可以驗證下:
class A
{
public:
A(){}
void showMember() const
{
std::cout << "a:" << a << std::endl;
}
private:
int a;
};
int main()
{
A a;
a.showMember();
}
運行結果是會變化的,此時我的IDE已經提示我,未初始化成員,因爲值不確定,會帶來不確定結果,良好的習慣是初始化他們。
成員初始化的方法
-
在構造函數內初始化
對於習慣C開發的我來說,順其自然的想到就是在構造函數裏面初始化這些成員。
A()
{
a = 0;
}
A(int v)
{
a = v;
}
-
使用成員初始化列表
但是,假設有一個buffer類,分配指定大小的buffer使用,通常會有一個成員記錄創建對象時buffer的大小,方便類中方法判斷對此塊內存的操作邊界,一般是固定的,所以爲了防止意外篡改,可以將此成員用const修飾。
class MyBuffer
{
public:
MyBuffer(int size) // <<===編譯錯誤
{
buffer = new char[size];
capacity = size; // <<===編譯錯誤
}
~MyBuffer()
{
delete[] buffer;
}
private:
char *buffer;
const int capacity; // <<===編譯錯誤
};
const修飾的變量是要在聲明時就初始化的,否則後面通過賦值來修改它的值就必然違背這個關鍵字設計的初衷了。那上面提到的構造函數裏面通過賦值的方式初始化const int capacity
就行不通了。C++11標準引入了構造函數成員初始化列表,寫法爲:
ClassName(para1, para2, ...):menber1(para1),menber2(para2),...
具體到Mybuffer就是:
explicit MyBuffer(int size) : capacity(size)
{
buffer = new char[size];
}
同樣的,對於聲明爲引用類型的成員,它也是要求聲明時就初始化好。初始化成員的方式還有一種寫法就是直接在聲明成員時。
class MyBuffer
{
private:
char *buffer;
const int capacity = 10;
//const int capacity{10};或者這種寫法
};
顯然,這種方法有一個比較大的缺點,無法通過構造函數動態的決定這個成員的值,類定義好後,它永遠是10,你改不了。
當然這兩種方式在彙編層面來說毫無差異:彙編對比
使用初始化列表的良好習慣
-
初始化列表的順序保持和成員聲明的順序一致。
看下面的例子:
class A
{
/*我的IDE已提示這個地方有問題
* |
* |
* V */
public:
A() : n2(0), n1(n2 + 1)
{
}
void print()
{
std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;
}
private:
int n1;
int n2;
};
int main()
{
A a;
a.print();
return 0;
}
運行結果:
n1: 32768, n2: 0
實際我們的預期是:A() : n2(0), n1(n2 + 1)
,n2 = 0; n1 = n2 + 1 = 0 + 1 = 1,爲什麼會這樣?這個和初始化列表賦值的順序有關,編譯器生成的初始化動作是按照類成員聲明的順序來的。所以編譯器會先初始化n1,n1 = n2 + 1
, 而此時n2還未初始化,是隨機值,這就導致n1也是隨機值,隨後n2 = 0
。也就是說如果成員初始化有依賴關係時,要特別注意初始化順序。
可以對比不同的初始化列表順序,發現他們的彙編都是按照類成員聲明屬性來賦值的。
explicit A(int p1, int p2) :a(p1),b(p2)
{
}
explicit A(int p1, int p2) :b(p2),a(p1)
{
}