C++中預設建構函式和建構函式初始化列表
1、預設建構函式和建構函式
(1)建構函式:C++用於構建類的新物件時需要呼叫的函式,該函式無返回型別!(注意:是“無”! 不是空!(void))。
(2)預設建構函式:預設建構函式是在呼叫時不需要顯示地傳入實參的建構函式。
一個類如果自己沒有定義建構函式,則會有一個無參且函式體也是空的預設建構函式。只要程式設計師定義了建構函式,編譯器就不會再提供預設構造函數了。
定義預設建構函式有兩種方式,一是定義一個無參的建構函式,二是定義所有引數都有預設值的建構函式。
class testClass { public: testClass(); /* 預設建構函式 */ testClass(int a, char b); /* 建構函式 */ testClass(int a=10,char b='c'); /* 預設建構函式 */ private: int m_a; char m_b; };
(3)編譯器在什麼情況下會生成預設建構函式?
下面幾種情況下,編譯需要生成預設建構函式:
- 當該類的類物件資料成員有預設建構函式時。
- 當該類的基類有預設建構函式時。
- 當該類的基類為虛基類時。
- 當該類有虛擬函式時。
(4)避免“無引數的預設建構函式”和“帶預設引數的預設建構函式”同時存在
無引數的預設建構函式和帶預設引數的預設建構函式同時存在時,編譯器會產生二義性,從而生成編譯錯誤
class Sample { public: // 預設建構函式 Sample() { // do something printf("Sample()"); } // 預設建構函式 Sample(int m = 10) { // do something printf("Sample(int m = 10)"); } }; int main() { Sample s; // error C2668: “Sample::Sample”: 對過載函式的呼叫不明確 return 0; }
2、建構函式初始列表
C++類中成員函式一般有兩種初始化,一種是在類的建構函式內部初始化,另一種是利用類的建構函式的初始化列表進行初始化。初始化和賦值對內建型別的成員沒有什麼大的區別。對非內建型別成員變數,為了避免兩次構造,推薦使用類建構函式初始化列表。
初始化資料成員與對資料成員賦值的含義是什麼?有什麼區別?
首先把資料成員按型別分類並分情況說明:
(1)內建資料型別,如int,float等;複合型別(指標,引用)
在成員初始化列表和建構函式體內進行,在效能和結果上都是一樣的
(2)使用者定義型別(類型別)
結果上相同,但是效能上存在很大的差別。因為類型別的資料成員物件在進入函式體前已經構造完成,也就是說在成員初始化列表處進行構造物件的工作,呼叫建構函式,在進入函式體之後,進行的是對已經構造好的類物件的賦值,又呼叫個拷貝賦值操作符才能完成(如果並未提供,則使用編譯器提供的預設按成員賦值行為)。因此,能使用初始化列表的時候儘量使用初始化列表。
class Test2
{
public:
Test1 test1 ;
Test2(Test1 &t1):test1(t1){} //呼叫拷貝建構函式初始化test1,省去了呼叫預設建構函式的過程
}
3、必須用帶有初始化列表的建構函式:
(1)成員型別是沒有預設建構函式的類。若沒有提供顯示初始化式,則編譯器隱式使用成員型別的預設建構函式,若類沒有預設建構函式,則編譯器嘗試使用預設建構函式將會失敗。
class Test1
{
public:
Test1(int a):i(a){}
int i;
};
class Test2
{
public:
Test1 test1 ;
Test2(Test1 &t1)
{test1 = t1 ;}
};
以上程式碼無法通過編譯,因為Test2的建構函式中test1 = t1這一行實際上分成兩步執行:
呼叫Test1的預設建構函式來初始化test1
由於Test1沒有預設的建構函式,所以1 無法執行,故而編譯錯誤。正確的程式碼如下,使用初始化列表代替賦值操作
class Test2
{
public:
Test1 test1 ;
Test2(int x):test1(x){}
}
(2)const成員或引用型別的成員。因為const物件或引用型別只能初始化,不能對他們賦值。
#include <iostream>
using namespace std;
class A
{
public:
#if 0
//因為成員變數中含有引用和常量則不能如此定義該建構函式
A(){
cout<<"aaaaaaaa"<<endl;
}
#endif
#if 1
A(int x1 = 0):x(x1),rx(x),pi(3.14) { }//帶有初始化列表的建構函式
void print()
{
cout<<"x="<<x<<" rx="<<rx<<" pi="<<pi<<endl;
}
~A(){}
private:
int x;
int ℞ //宣告為引用時則必須在建構函式基/成員初始值設定項列表中初始化
const double pi;//宣告為const時則必須在建構函式基/成員初始值設定項列表中初始化
#endif
};
int main()
{
A a(20);
a.print();
A a1; //因為建構函式中有x1預設實參(int x1 = 111),則可以如此呼叫
a1.print();
system("pause");
return 0;
}
注:如果一個建構函式為所以引數都提供預設實參,則它實際上也定義了預設建構函式
(3)基類未宣告預設建構函式
#include <iostream>
using namespace std;
class Animal
{
public:
Animal(int weight,int height): //基類未宣告預設建構函式
m_weight(weight),m_height(height){
cout<<"Animal "<<"weight="<<weight<<" height="<<height<<endl;
}
private:
int m_weight;
int m_height;
};
class Dog: public Animal
{
public:
Dog(int weight = 10,int height = 20,int type = 1):
Animal(weight,height){//必須使用初始化列表增加對父類的初始化,否則父類Animal無合適建構函式
cout<<"Dog"<<endl;
}
private:
int m_type;
};
int main()
{
Dog d;
system("pause");
return 0;
}
最簡單的解決方法是將Animal的建構函式宣告為:Animal(int weight = 0,int height = 0);
4、初始化列表的成員初始化順序:
C++初始化類成員時,是按照宣告的順序初始化的,而不是按照出現在初始化列表中的順序。
class CMyClass {
CMyClass(int x, int y);
int m_x;
int m_y;
};
CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){
}
你可能以為上面的程式碼將會首先做m_y=I,然後做m_x=m_y,最後它們有相同的值。但是編譯器先初始化m_x,然後是m_y,,因為它們是按這樣的順序宣告的。結果是m_x將有一個不可預測的值。有兩種方法避免它,一個是總是按照你希望它們被初始化的順序宣告成員,第二個是,如果你決定使用初始化列表,總是按照它們宣告的順序羅列這些成員。這將有助於消除混淆。
如果可以的話儘可能避免使用某些成員初始化其他成員。而最好用建構函式的引數作為成員的初始值,這樣做的好處是可以比考慮成員的初始化順序。例如以上可改為:
class CMyClass {
CMyClass(int x, int y);
int m_x;
int m_y;
};
CMyClass::CMyClass(int x, int y) : m_x(x), m_y(y) {
}