C++中的對象初始化

  

當對象在建立時得到了一個特定的值,咱們說這個對象被初始化。初始化不是賦值,初始化的含義是建立變量賦予其一個初始值,而賦值的含義是把當前值擦除,而以一個新值來替代。對象初始化能夠分爲默認初始化、直接初始化、拷貝初始化以及值初始化。html

 

// (1)默認初始化
int i1;//默認初始化,在函數體以外(初始化爲0)  


int f(void)  
{  
int i2;//不被初始化,若是使用此對象則報錯  
}  

string empty;//empty非顯示的初始化爲一個空串,調用的是默認構造函數  

// (2)拷貝初始化
string str1(10,'9');//直接初始化  
string str2(str1);//直接初始化  
string str3 = str1;//拷貝初始化  

 // (3)值初始化
vector<int> v1(10);//10個元素,每一個元素的初始化爲0  
vector<string> v2(10);//10個元素,每一個元素都爲空   

int *pi = new int;//pi指向一個動態分配的,未初始化的無名對象  
string *ps = new string;//初始化爲空string  
int *pi = new int;//pi指向一個未初始化的int  

int *pi = new int(1024);//pi指向的對象的值爲1024  
string *ps = new string(10,'9');//*ps爲"9999999999"  


string *ps1 = new string;//默認初始化爲空string  
string *ps2 = new string();//值初始化爲空string  
int *pi1 = new int;//默認初始化  
int *pi2 = new int();//值初始化爲0   

 

 

一、C++ Copy初始化

在《inside the c++ object model》一書中談到copy constructor的構造操做,有三種狀況下,會以一個object的內容做爲另外一個object的初值:ios

  1. 第一種狀況: XX aa = a;  
  2. 第二種狀況: XX aa(a);  
  3. 第三種狀況: extern fun(XX aa); fun(a)函數調用  
  4. 第四種狀況: XX fun(){...}; XX a = fun();函數返回值的時候 

下面咱們就上述的四種狀況來一一驗證:c++

    class ClassTest{ public: ClassTest()//定義默認構造函數 
 { c[0] = '\0'; cout << "ClassTest()" << endl; } ClassTest& operator=(const ClassTest &ct) //重載賦值操做符 
 { strcpy_s(c, ct.c); cout << "ClassTest& operator=(const ClassTest &ct)" << endl; return *this; } ClassTest(const char *pc) { strcpy_s(c, pc); cout << "ClassTest (const char *pc)" << endl; } ClassTest(const ClassTest& ct)//複製構造函數 
 { strcpy_s(c, ct.c); cout << "ClassTest(const ClassTest& ct)" << endl; } private: char c[256]; }; ClassTest func(ClassTest temp){ return temp; } int main(){ cout << "ct1: "; ClassTest ct1("ab");//直接初始化 
        cout << "ct2: "; ClassTest ct2 = "ab";//複製初始化 
        /*輸出說明: ClassTest ct2 = "ab"; 它原本是要這樣來構造對象的:首先調用構造函數ClassTest(const char *pc)函數建立一個臨時對象, 而後調用複製構造函數,把這個臨時對象做爲參數,構造對象ct2。然而編譯也發現,複製構造函數是 公有的,即你明確地告訴了編譯器,你容許對象之間的複製,並且此時它發現能夠經過直接調用重載的 構造函數ClassTest(const char *pc)來直接初始化對象,而達到相同的效果,因此就把這條語句優化爲 ClassTest ct2("ab")。 */ cout << "ct3: "; ClassTest ct3 = ct1;//複製初始化 
        cout << "ct4: "; ClassTest ct4(ct1);//直接初始化 
        cout << "ct5: "; ClassTest ct5 = ClassTest();//複製初始化 
        cout << "ct6: "; ClassTest ct6;//複製初始化 
        ct6 = "caoyan is a good boy!"; cout << "ct7: "; ClassTest ct7; ct7 = func(ct6); return 0; } 

測試結果:程序員

咱們能夠看到,比較複雜的是ct6和ct7,其中ct6仍是比較好理解的,ct7這種狀況比較難懂,爲何會有兩個拷貝構造函數的調用????ide

第一次拷貝構造函數的調用:第一次很簡單,是由於函數參數的傳遞,將ct6做爲參數傳遞給temp,用ct6的值初始化temp會調用拷貝構造函數;函數

第二次拷貝構造函數的調用:由於要返回一個ClassTest對象,咱們的編譯器怎麼作????首先它將temp對象拷貝到func函數的上一級棧幀中,它的上一級棧幀是main函數的棧幀,那麼當函數返回時,參數出棧,temp對象的內存空間就會被收回,可是它的值已經被拷貝到main棧幀的一個預留空間中,因此從temp到預留空間的拷貝也是調用拷貝構造函數,最後一步就是給ct7賦值,毫無疑問調用賦值構造函數;對棧幀不一樣的同窗能夠看看《程序員的自我修養》一書,裏面講得很詳細!post

二、初始化列表、構造函數與=賦值之間的區別

總所周知,C++對象在建立之時,會由構造函數進行一系列的初始化工做。以沒有繼承關係的單個類來看,除了構造函數自己的產生與指定,還涉及到初始化步驟,以及成員初始化方式等一些細節,本篇筆記主要對這些細節進行介紹,弄清C++對象在初始化過程當中一些基本運行規則。測試

構造函數指定
一般,咱們在設計一個類的時候,會爲這個類編寫對應的default constructor、copy constructor、copy assignment operator,還有一個deconstructor。即使咱們僅僅編寫一個空類,編譯器在編譯時仍舊會爲其默認聲明一個default constructor、copy constructor、copy assignment operator與deconstructor,若是在代碼裏面存在着它們的使用場景,那麼這個時候編譯器纔會建立它們。
class MyCppClass {}
一旦咱們爲一個類編寫了default constructor,那麼編譯器也就不會爲其默認生成default constructor,對於其餘幾個函數也同樣。對於編譯器默認生成的constructor來講,它會以必定規則對每個數據成員進行初始化。考慮到成員初始化的重要性,在編寫本身的constructor時就須要嚴謹認真了,特別是在類的派生與繼承狀況下這點顯得尤其重要。對於copy constructor和assignment operator的運用場景,這裏不得很少說一點,見以下代碼:
#include <iostream>
 
using std::cout; using std::endl; class MyCppClass { public: MyCppClass() { std::cout <<"In Default Constructor!" <<std::endl; } MyCppClass(const MyCppClass& rhs) { std::cout <<"In Copy Constructor!" <<std::endl; } MyCppClass& operator= (const MyCppClass& rhs) { std::cout <<"In Copy Assignment Operator!" <<std::endl; return *this; } }; int main() { MyCppClass testClass1; // default constructor
    MyCppClass testClass2(testClass1);     // copy constructor
    testClass1 = testClass2;               // copy assignment operator
 MyCppClass testClass3 = testClass1;    // copy constructor
 
    return 0; }

執行結果:優化

 
這裏須要注意的是,通常狀況下咱們老是覺得在‘=’運算符出現的地方都是調用copy assignment operator,上面這種狀況倒是個例外。也就是,當一個新對象被定義的時候,即使這個時候是使用了'='運算符,它真實調用的是初始化函數copy constructor,而不是調用copy assignment operator去進行賦值操做。
 
Why初始化列表
一個對象在初始化時包括了兩個步驟:
首先,分配內存以保存這個對象;
其次,執行構造函數。
在執行構造函數的時候,若是存在有初始化列表,則先執行初始化列表,以後再執行構造函數的函數體。那麼,爲何會引入初始化列表呢?
 
C++與C相比,在程序組織上由「以函數爲基本組成單位的面向過程」變遷到「基於以爲中心的面向對象」,與此同時類也做爲一種複合數據類型,而初始化列表無非就是進行一些數據的初始化工做。考慮到這裏,也能夠較爲天然的推測初始化列表與類這種數據類型的初始化有着關聯。
在引入初始化列表以後,一個類對應數據成員的初始化就存在有兩種方式。下面是類的數據成員類型分別爲內置類型、自定義類型時的一個對比。 
// 數據成員類型爲內置類型
class MyCppClass { public: // 賦值操做進行成員初始化
 MyCppClass { counter = 0; } // 初始化列表進行成員初始化
    MyCppClass : counter(0) { } private: int counter; }

當類的數據成員類型爲內置類型時,上面兩種初始化方式的效果同樣。當數據成員的類型一樣也爲一個類時,初始化的過程就會有不同的地方了,好比: this

// 數據成員類型爲自定義類型:一個類
class MyCppClass { public: // 賦值操做進行成員初始化
    MyCppClass(string name) { counter = 0; theName = name; } // 初始化列表進行成員初始化
    MyCppClass : counter(0), theName(name) { } private: int counter; string theName; }

 

在構造函數體內的theName = name這條語句,theName先會調用string的default constructor進行初始化,以後再調用copy assignment opertor進行拷貝賦值。而對於初始化列表來講,直接經過copy constructor進行初始化

 

明顯起見,能夠經過以下的代碼進行測試。

#include <iostream> #include <string>
 
class SubClass { public: SubClass() { std::cout <<" In SubClass Default Constructor!" <<std::endl; } SubClass(const SubClass& rhs) { std::cout <<" In SubClass Copy Constructor!" <<std::endl; } SubClass& operator= (const SubClass& rhs) { std::cout <<" In SubClass Copy Assignment Operator!" <<std::endl; return *this; } }; class BaseClass { public: BaseClass(const SubClass &rhs) { counter = 0; theBrother = rhs; std::cout <<" In BaseClass Default Constructor!" <<std::endl; } BaseClass(const SubClass &rhs, int cnt):theBrother(rhs),counter(cnt) { std::cout <<" In BaseClass Default Constructor!" <<std::endl; } BaseClass(const BaseClass& rhs) { std::cout <<" In BaseClass Copy Constructor!" <<std::endl; } BaseClass& operator= (const BaseClass& rhs) { std::cout <<" In BaseClass Copy Assignment Operator!" <<std::endl; return *this; } private: int counter; SubClass theBrother; }; int main() { SubClass subClass; std::cout <<"\nNo Member Initialization List: " <<std::endl; BaseClass BaseClass1(SubClass); std::cout <<"\nMember Initialization List: " <<std::endl; BaseClass BaseClass2(SubClass, 1); return 0; }

執行結果:

 
也就是,在涉及到自定義類型初始化的時候,使用初始化列表來完成初始化在效率上會有着更佳的表現。這也是初始化列表的一大閃光點。即使對於內置類型,在一些狀況下也是須要使用初始化列表來完成初始化工做的,好比const、references成員變量。這裏有篇筆記,對初始化列表有着很是詳盡的描述。
 
幾個初始化名詞
在閱讀《Accelerated C++》中文版時,老是碰到「缺省初始化」、「隱式初始化」以及「數值初始化」,最初在理解這幾個名詞的時候幾費周折,總以爲爲何一個初始化操做造出瞭如此多的名詞,爲此沒少花時間來弄清楚它們之間的關係。

爲了更好的理解它們,先對C++當中的數據類型進行簡單劃分。在C++裏面,數據類型大體能夠分爲兩種:第一種是內置類型,好比float, int, double等;第二種是自定義類型,也就是咱們經常使用的class, struct定義的類。在對這些類型的數據進行初始化時,差異就體現出來了:對於內置類型,在使用以前必須進行顯示的初始化,而對於自定義類型,初始化責任則落在了構造函數身上。 
int x = 0; // 顯示初始化x SubClass subClass; // 依賴SubClass的default constructor進行初始化

上面的名詞「缺省初始化」描述的就是當內置類型或者自定義類型的數據沒有進行顯示初始化時的一種初始化狀態。而「隱式初始化」描述的是在該狀態下面進行的具體操做方式,好比對於內置類型來講,缺省初始化狀態下進行的隱式初始化其實是未定義的,而自定義類型的隱式初始化則依賴於其constructor。

 
前面提到過C++不保證內置類型的初始化,可是當內置類型在做爲一個類的成員時,在某些特定的條件下該內置類型的成員會被編譯器主動進行初始化,對於這個過程也就是所謂的數值初始化。在《Accelerated C++》當中列出了以下的幾種狀況:
  1. 對象被用來初始化一個容器元素
  2. 爲映射表添加一個新元素,對象是這個添加動做的反作用
  3. 定義一個特定長度的容器,對象爲容器的元素
測試以下:
#include <iostream> #include <vector> #include <map> #include <string> 
 
using std::cout; using std::endl; using std::vector; using std::map; using std::string; class NumbericInitTestClass { public: void PrintCounter() { cout <<"counter = " <<counter <<endl; } private: int counter; }; int main() { NumbericInitTestClass tnc; tnc.PrintCounter(); map<string, int> mapTest; cout <<mapTest["me"] <<endl; vector<NumbericInitTestClass> vecNumbericTestClass(1); vecNumbericTestClass[0].PrintCounter(); return 0; }
對於沒有進行初始化的內置類型,是一個未定義的值2009095316,而對於2, 3種狀況來講,均被初始化爲0,對於第1種狀況我尚未想到合適的場景。
 
回過頭想一想,爲了書中的一些類似的名詞,去想辦法把它們湊在一塊兒老是顯得有些牽強附會:) 
 
一些規則
這裏附上幾條有關初始化的基本規則,它們多來源於《Effective C++》
 
1. 爲內置型對象進行手工初始化,由於C++不保證初始化它們。
2. 構造函數最好使用成員初值列(member initialization list),而不要在構造函數體內使用賦值操做。初值列列出的成員變量,其排列次序應該和它們在class中聲明的次序相同。
3. C++不喜歡析構函數吐出異常。
4. 在構造函數與析構函數期間不要調用virtual函數,由於這類調用從不降低至derived class。
5. copying函數應該確保複製「對象內全部成員變量」及「全部base class成分」。
 
 
 

參考文章

C++中對象初始化方式

相關文章
相關標籤/搜索