對於常量類型和引用類型的數據成員,不能在構造函數中用賦值語句直接賦值,C++提供初始化表進行置初值。
帶有成員初始化表的構造函數的一般形式如下:
類名::構造函數名([參數表])[:(成員初始化表)]
{
// 構造函數體
}
成員初始化表的一般形式為:
數據成員名1(初始值1),數據成員名2(初始值2),……
C++構造函數中初始化成員參數列表初始化成員(必須用的原因:對象成員的初始化,const修飾的成員的初始化,引用成員的初始化,子類調用父類的構造函數初始化父類成員)參數列表在構造函數執行之前執行,參數列表中執行的是初始化(所有的成員,無論是否出現在參數列表中,都會有初始化),參數列表的執行順序與類中成員的聲明順序,與類的繼承順序相一致構造函數中執行的一般是賦值多重繼承,虛繼承構造函數的參數初始化列表的區別
類對象的構造順序是這樣的:
1.分配內存,調用構造函數時,隱式/顯示的初始化各數據成員;
2.進入構造函數后在構造函數中執行一般賦值與計算。
使用初始化列表有兩個原因:
原因1.必須這樣做:
在以下三種情況下需要使用初始化成員列表:
一、需要初始化的數據成員是對象的情況(這里包含了繼承情況下,通過顯示調用父類的構造函數對父類數據成員進行初始化);
二、需要初始化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){}才可以。