1. 程式人生 > >C++建構函式中用引數列表初始化成員

C++建構函式中用引數列表初始化成員

C++建構函式中初始化成員引數列表初始化成員(必須用的原因:物件成員的初始化,const修飾的成員的初始化,引用成員的初始化,子類呼叫父類的建構函式初始化父類成員)引數列表在建構函式執行之前執行,引數列表中執行的是初始化(所有的成員,無論是否出現在引數列表中,都會有初始化),引數列表的執行順序與類中成員的宣告順序,與類的繼承順序相一致建構函式中執行的一般是賦值多重繼承,虛繼承建構函式的引數初始化列表的區別
類物件的構造順序是這樣的:
1.分配記憶體,呼叫建構函式時,隱式/顯示的初始化各資料成員;
2.進入建構函式後在建構函式中執行一般賦值與計算。
使用初始化列表有兩個原因:
原因1.必須這樣做:
《C++ Primer》中提到在以下三種情況下需要使用初始化成員列表:

一、需要初始化的資料成員是物件的情況(這裡包含了繼承情況下,通過顯示呼叫父類的建構函式對父類資料成員進行初始化);
二、需要初始化const修飾的類成員;
三、需要初始化引用成員資料;
即:

例一、資料成員是物件,切物件只有含引數的建構函式;
如果我們有一個類成員,它本身是一個類或者是一個結構,而且這個成員它只有一個帶引數的建構函式,而沒有預設建構函式,這時要對這個類成員進行初始化,就必須呼叫這個類成員的帶引數的建構函式,如果沒有初始化列表,那麼他將無法完成第一步,就會報錯。

class Test
{
public:
         Test(int x,int
y,int z); private: int a; int b; int c; }; class MyTest { public: MyTest():test(1,2,3){} //初始化,初始化列表在建構函式執行前執行(這個可以測試,對同一個變數在初始化列表和建構函式中分別初始化,首先執行引數列表,後在函式體內賦值,後者會覆蓋前者)。 private: Test test; //宣告 };

因為Test有了顯示的帶引數的建構函式,那麼他是無法依靠編譯器生成無參建構函式的,所以沒有三個int型資料,就無法建立Test的物件。
Test類物件是MyTest的成員,想要初始化這個物件test,那就只能用成員初始化列表,沒有其他辦法將引數傳遞給Test類建構函式。

例二、物件引用或者cosnt修飾的資料成員
另一種情況是這樣的:當類成員中含有一個const物件時,或者是一個引用時,他們也必須要通過成員初始化列表進行初始化,因為這兩種物件要在聲明後馬上初始化,而在建構函式中,做的是對他們的賦值,這樣是不被允許的。
即:

class Test
{
     priate:
          const int a;   //const成員宣告
     public:
          Test():a(10){}  //初始化
};
或
class Test
{
    private:
         int &a;   //宣告
    public:
         Test(int a):a(a){}  //初始化
}

例三、子類初始化父類的私有成員,需要在(並且也只能在)引數初始化列表中顯示呼叫父類的建構函式,如下:

class Test
{
 private:
      int a;
      int b;
      int c;
 public:
      Test(int a,int b,int c)
      {
           this->a = a;
           this->b = b;
           this->c = c;
      }
      int getA(){return a;}
      int getB(){return b;}
      int getC(){return c;}
};
class MyTest:public Test
{
 private:
      int d;
 public:
      MyTest(int a,int b,int c,int d):Test(a,b,c)
      //MyTest(int a,int b,int c,int d)
      { 
          //Test(a,b,c);   //建構函式只能在初始化列表中被顯示呼叫,不能在建構函式內部被顯示呼叫
       this->d = d;
      }
      int getD(){return d;}
}; 

int main(int argc,char *argv[])
{
     MyTest mytest(1,2,3,4);
     printf("a=%d,b=%d,c=%d,d=%d\n",
     mytest.getA(),mytest.getB(),mytest.getC(),mytest.getD());
     return 0;
}

多重繼承,虛繼承建構函式的引數初始化列表的區別:
程式碼如下:
注意多重繼承子類的建構函式

#include <stdio.h>

class CTop
{
private:
    int a;
public:
    int getA()
    {
        return a;
    }
    CTop(int a)
    {
        this->a  = a;
    }
};

class CLeft:public CTop
{
private:
    int b;
public:
    int getL()
    {
        return b;
    }
    CLeft(int a,int b):CTop(a)
    {
        this->b = b;
    }
};

class CRight:public CTop
{
private:
    int c;
public:
    int getR()
    {
        return c;
    }
    CRight(int a,int c):CTop(a)
    {
        this->c = c;
    }
};

class Test:public CLeft,public CRight
{
private:
    int d;
public:
    int getT()
    {
        return d;
    }
    Test(int a,int b,int c,int d):CLeft(a,b),CRight(a,c)
    {
        this->d = d;
    }
};

int main(int argc,char *argv[])
{
    Test obj(1,2,3,4);
     printf("obj.a=%d,obj.b=%d,obj.c=%d,obj.d=%d\n",
     obj.CLeft::getA(),obj.getL(),obj.getR(),obj.getT());   //getA有歧義,要用類名來做區分。
    return 0;
}

注意虛繼承子類的建構函式

#include <stdio.h>

class CTop
{
private:
    int a;
public:
    int getA()
    {
        return a;
    }
    CTop(int a)
    {
        this->a  = a;
    }
};

class CLeft:virtual public CTop
{
private:
    int b;
public:
    int getL()
    {
        return b;
    }
    CLeft(int a,int b):CTop(a)
    {
        this->b = b;
    }
};

class CRight:virtual public CTop
{
private:
    int c;
public:
    int getR()
    {
        return c;
    }
    CRight(int a,int c):CTop(a)
    {
        this->c = c;
    }
};

class Test:public CLeft,public CRight
{
private:
    int d;
public:
    int getT()
    {
        return d;
    }
    Test(int a,int b,int c,int d):CLeft(a,b),CRight(a,c),CTop(a)
    {
        this->d = d;
    }
};

int main(int argc,char *argv[])
{
    Test obj(1,2,3,4);
    printf("obj.a=%d,obj.b=%d,obj.c=%d,obj.d=%d\n",
    obj.getA(),obj.getL(),obj.getR(),obj.getT());      //因為採用虛基類,虛繼承機制保證了a只有一份,所以不存在歧義。
    return 0;
}

原因2.效率要求這樣做:
類物件的構造順序顯示,進入建構函式體後,進行的是計算,是對成員變數的賦值操作,顯然,賦值和初始化是不同的,這樣就體現出了效率差異,如果不用成員初始化類表,那麼類對自己的類成員分別進行的是一次隱式的預設建構函式的呼叫,和一次賦值操作符的呼叫,如果是類物件,這樣做效率就得不到保障。
注意:建構函式需要初始化的資料成員,不論是否顯示的出現在建構函式的成員初始化列表中,都會在該處完成初始化,並且初始化的順序和其在類中宣告時的順序是一致的,與列表的先後順序無關,所以要特別注意,保證兩者順序一致才能真正保證其效率和準確性。
為了說明清楚,假設有這樣一個類:

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

1、foo(){}和foo(int i = 0){}都被認為是預設建構函式,因為後者是預設引數。兩者不能同時出現。
2、建構函式列表的初始化方式不是按照列表的的順序,而是按照變數宣告的順序。比如foo裡面,a在b之前,那麼會先構造a再構造b。所以無論foo():a(b + 1), b(2){}還是foo():b(2),a(b+1){}都不會讓a得到期望的值。
3、建構函式列表能夠對const成員初始化。比如foo裡面有一個int const c;則foo(int x) : c(x){}可以讓c值賦成x。
不過需要注意的是,c必須在每個建構函式(如果有多個)都有值。
4、在繼承裡面,只有初始化列表可以構造父類的private成員(通過顯示呼叫父類的建構函式)。比如說:

class child : public foo
{
};

foo裡面的建構函式是這樣寫的:

foo (int x)
{
       a = x;
}.

而在child裡面寫child(int x){ foo(x); }是通過不了編譯的。
只有把子類建構函式寫作child (int x) : foo(x){}才可以。