C++語言中必須使用【初始化列表】初始化數據成員的三種情況

類對象的構造順序是這樣的

1.分配內存,調用構造函數時,隱式/顯示的初始化各數據成員;

2.進入構造函數後在構造函數中執行一般賦值與計算。

使用初始化列表有兩個原因

原因1.必須這樣做:

《C++ Primer》中提到在以下三種情況下需要使用初始化成員列表:

情況一、需要初始化的數據成員是對象的情況(這裏包含了繼承情況下,通過顯示調用父類的構造函數對父類數據成員進行初始化);

情況二、需要初始化const修飾的類成員或初始化引用成員數據;

情況三、子類初始化父類的私有成員;

  • 情況一的說明:數據成員是對象,並且這個對象只有含參數的構造函數,沒有無參數的構造函數;

如果我們有一個類成員,它本身是一個類或者是一個結構,而且這個成員它只有一個帶參數的構造函數,而沒有默認構造函數,這時要對這個類成員進行初始化,就必須調用這個類成員的帶參數的構造函數,如果沒有初始化列表,那麼他將無法完成第一步,就會報錯。

舉個栗子:

#include <iostream>
using namespace std;

class Test
{
 public:
    Test (int, int, int)
    {
        cout <<"Test" << endl;
    };
 private:
    int x;
    int y;
    int z;
};
class Mytest
{
 public:
    Mytest():test(1,2,3)
    {
        //初始化  
        cout << "Mytest" << endl;
    };
private:
    Test test; //聲明  
}; 
int main()
{
    Mytest test;
    return 0;
}

① 如果沒有mytest():test(1,2,3){}初始化列表就會報錯:

C++語言中必須使用【初始化列表】初始化數據成員的三種情況
因爲Test有了顯示的帶參數的構造函數,那麼他是無法依靠編譯器生成無參構造函數的,所以沒有三個int型數據,就無法創建Test的對象。Test類對象是MyTest的成員,想要初始化這個對象test,那就只能用成員初始化列表,沒有其他辦法將參數傳遞給Test類構造函數。

②初始化列表在構造函數執行前執行(這個可以看上面的結果,對同一個變量在初始化列表和構造函數中分別初始化,首先執行參數列表,後在函數體內賦值,後者會覆蓋前者)。

  • 情況二的說明:對象引用或者cosnt修飾的數據成員
    當類成員中含有一個const對象時,或者是一個引用時,他們也必須要通過成員初始化列表進行初始化,因爲這兩種對象要在聲明後馬上初始化,而在構造函數中,做的是對他們的賦值,這樣是不被允許的。
    舉個栗子:
#include <iostream>
using namespace std;
// const 
class Test 
{  
 priate:  
    const int a;             //const成員聲明  
 public:  
    Test():a(10){}           //初始化  
};   
// 引用 
class Test  
{  
 private:  
     int &a;                        //聲明  
 public:  
     Test(int a):a(a){}        //初始化  
}  
  • 情況三的說明:子類初始化父類的私有成員,需要在(並且也只能在)參數初始化列表中顯示調用父類的構造函數:
    舉個栗子:
#include <iostream>
using namespace std;
class Test{
public:
    Test(){ cout << "111111111";};
    Test (int x){ int_x = x;};
    void show(){cout<< int_x << endl;}
private:
    int int_x;
};
class Mytest:public Test{
public:
    Mytest() :Test(110)
    {
        // 構造函數只能在初始化列表中被顯示調用(如果不顯示初始化,默認調用默認構造函數),不能在構造函數內部被顯示調用
        // 內部調用的是臨時變量
        //Test(110);            
    };
}; 
int main()
{
 Test *p = new Mytest();
 p->show();
 return 0;
}

結果:如果在構造函數內部被顯示調用輸出結果是:-842150451(原因是這裏調用了無參構造函數);

            如果在初始化列表中被顯示調用輸出結果是:110

原因2.效率要求這樣做:

     類對象的構造順序顯示,進入構造函數體後,進行的是計算,是對成員變量的賦值操作,顯然,賦值和初始化是不同的,這樣就體現出了效率差異,如果不用成員初始化類表,那麼類對自己的類成員分別進行的是一次隱式的默認構造函數的調用,和一次賦值操作符的調用,如果是類對象,這樣做效率就得不到保障。

注意:構造函數需要初始化的數據成員,不論是否顯示的出現在構造函數的成員初始化列表中,都會在該處完成初始化,並且初始化的順序和其在類中聲明時的順序是一致的,與列表的先後順序無關,所以要特別注意,保證兩者順序一致才能真正保證其效率和準確性。

爲了說明清楚,假設有這樣一個類:

class foo
{
 private:
   int a, b;
};

①、foo(){}和foo(int i = 0){}都被認爲是默認構造函數,因爲後者是默認參數。兩者不能同時出現。

②構造函數列表的初始化方式不是按照列表的的順序,而是按照變量聲明的順序。比如foo裏面,a在b之前,那麼會先構造a再構造b。所以無論foo():a(b + 1), b(2){}還是foo():b(2),a(b+1){}都不會讓a得到期望的值。

③構造函數列表能夠對const成員初始化。比如foo裏面有一個int const c;則foo(int x) : c(x){}可以讓c值賦成x。

  不過需要注意的是,c必須在每個構造函數(如果有多個)都有值。

④在繼承裏面,只有初始化列表可以構造父類的private成員(通過顯示調用父類的構造函數)。比如說:

class child : public foo{};

foo裏面的構造函數是這樣寫的:

foo (int x)
{
  a =x;
}

而在child裏面寫child(int x){ foo(x); }是通過不了編譯的。

只有把子類構造函數寫作child(int x) : foo(x){}纔可以。

原文鏈接:https://blog.csdn.net/sinat_20265495/article/details/53670644

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