初始化與賦值之間的區別

  由於在內置類型中,初始化和賦值不進行區分並不會造成很大的影響,所以讓我很大程度上忽略了他們的差別。

  直到C++primer中對於構造函數提出了一句話,構造函數有一個初始化部分和一個函數體,在一個構造函數中,成員的初始化是在函數體執行之前完成的,且按照它們在類中出現的順序進行初始化。讓我第一次開始思考初始化和賦值操作之間的區別。

  實際上,我們要注意很重要的一點,我們平時的對於構造函數中使用的“賦值”寫法實際上並沒有對於類內的成員進行初始化,如果類內的成員是已經定義默認構造函數的非內置類型,像string類型,那麼我們即使用的是“賦值”寫法,那麼我們沒有進行初始化操作也沒有問題,因爲默認構造函數會幫助我們完成構造過程,但是如果是沒有定義默認構造函數的內置類型,編譯器是是不負責初始化的。而對於一些非內置類型的話,用賦值的寫法理論上也沒有問題,因爲實際上也會被初始化。但是爲了避免出錯,我們都建議用初始化列表的方式先進行初始化。

  首先我們看構造函數的兩種寫法

  “賦值”寫法:

//例1:構造函數的一種寫法,雖然合法但比較草率,沒有使用構造函數初始值
Sales_data::Sales_data(const string &s,unsigned cnt,double price){
	bookNo=s;
	units_sold=cnt;
	revenue=cnt*price;
}
  然後是初始化列表初試化類對象

//例2:
Sales_data(const std::string &s,unisigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}

  對比兩種構造函數的寫法,在第一種情況下,如果類內的成員都是內置類型的話,是不會進行初始化的,而在第二種情況下,用初始化列表的情況下(實際上我們可以看得到就是我們在使用初始化列表的情況下,用括號進行初始化,對於一些非內置類型的其實是調用了各個類型的複製構造函數來進行初始化的,這些類型不經過初始化是無法使用的,因而我們說對於這些成員,初始化和賦值之間的區別是巨大的,而對於一般的內置類型,初始化和賦值是沒有明顯區別的)  

  當我們在定義變量時習慣立即對其進行初始化,而非先定義再賦值

string foo="helloworld";//定義並初始化
string bar;//默認初始化成空string對象
bar="helloworld";//爲bar賦一個新值
  就對象的數據成員而言,初始化和賦值也有類似的區別,如果沒有在構造函數的初始值列表中顯式地初始化成員,則該成員將在構造函數體之前執行默認初始化。
  實際上對於一些特定變量(const變量和引用型變量),對於這些變量,是不允許賦值操作的:

class ConstRef{
	...//省略掉其餘的成員
private:
	int i;
	const int ci;
	int &ri;
};
//構造函數部分,這樣寫顯然是要報錯的,因爲const和引用對象是無法賦值的,只能進行初始化
<pre name="code" class="cpp">ConstRef::ConstRef(int ii){
	i=ii;
	ci=ii;
	ri=i;
}


  所以在這種情況下,我們就只能列表初始化的方式來書寫構造函數,即

ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(ii){}

  實際上,我們在一些情況下對比上面的兩個例子中的兩個構造函數,就可以發現賦值和初始化兩個操作的區別在構造函數中激化的是比較嚴重的,上面提到的const和引用變量是第一種例子,實際上還有一種情況我們在講合成得默認構造函數的侷限性的時候也有提到。

  在C++primer P236頁中曾經提到合成得默認構造函數的侷限性的時候,提到了第三點,當對於類A,如果程序員定義了構造函數,也就是編譯器此時並不會生成合成默認構造函數,在沒有顯式指出默認構造函數的時候,如果我們在類B中將類A作爲類B的成員的時候,那麼對於類B就無法生成合成的默認構造函數,因爲在這種嵌套的方式定義的B類中,B類中要想生成合成默認構造函數,實際上是必須要依賴於默認構造函數的。

  實際上,如果我們要在B類中寫構造函數,如果想要採用上面例1的方式,用等號來處理的話,是不可以的!因爲就像上文所說的,如果像上面例1的方式來處理,那麼在函數體之前需要對類B(包含類B中的類A)進行初始化的(注意,不是賦值),而這種初始化是依賴於默認構造函數的!所以我們如果在此時使用所謂“賦值”的寫法企圖達到初始化的目的是不可能的。而沒有進行初始化的相關成員是無法使用的,所以這種時候就只能用列表初始化的方式來書寫構造函數。如下:

class A {    
public:
    A(int p) { ... }
//省略了一些成員
};
class B {
    A m;//同樣的省略了一些成員
public:
    B();
}; 
//此時對於類B就只能用列表初始化的方式來寫構造函數 
B::B(): m(2)
{
    ...
} 

  在上面我們就可以看到列表初始化的好處所在,因爲列表初始化是依賴於複製構造函數的,而不管我們有沒有在類中定義這個類的複製構造函數,類中都會自己生成一個默認複製構造函數,而如果我們想要用”賦值“的方式妄圖實現所謂的初始化的話,其實這種初始化時依賴於默認構造函數的(也就是沒有參數的構造函數,或者是那種在參數表中指明瞭自己可設可不設參數的構造函數),因此在程序員自己設置了任何一個有參數的默認構造函數且並沒有自己定義默認構造函數的時候就會出現問題。

  以上是推薦我們使用列表初始化方式進行初始化的原因之一

  其二在於如果我們採用賦值的方式妄圖進行初始化的話,對於一些非內置類型進行的步驟就是先進行初始化後進行賦值,而如果僅僅是進行列表初始化的話,就只會進行初始化這一種個操作,而並不會進行賦值,故首先效率上列表初始化時是會更高。對於內置類型雖然是沒有區別,但是如果在構造函數中先在列表那裏寫初始化,再在函數體中寫內置類型的操作,是有些奇怪的,因此爲了統一性,推薦統一使用列表初始化方式進行成員的初始化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章