預設建構函式
如果你定義一個類,並且沒有給它定義建構函式。編譯器會為這個類提供預設的建構函式。如果你提供了建構函式,編譯器是不會再為你提供一個預設建構函式的。編譯器提供的預設建構函式什麼都沒做。類的成員變數將遵守預設的初始化規則。
編譯器提供的預設建構函式的初始化規則:
在棧和堆中的類對象的內建或複合類型成員變數將為髒資料;
在全域變數區的類對象的內建或複合類型成員變數初始化為0;
類對象成員將調用預設的建構函式來初始化;
#include <iostream>using namespace std;class Box{public:int length;int width;};Box box1;int main(){Box box2;Box *pbox3 = new Box;cout<<"box1.length == "<<box1.length<<" box1.width == "<<box1.width<<endl;cout<<"box2.length == "<<box2.length<<" box2.width == "<<box2.width<<endl;cout<<"box3.length == "<<pbox3->length<<" box3.width == "<<pbox3->width<<endl;return 0;}
上面代碼的結果為:
box1.length == 0 box1.width == 0
box2.length == 2686792 box2.width == 1987092020
box3.length == 3811912 box3.width == 3801284
帶預設實參的建構函式
就像對普通函數一樣可以為建構函式的參數指定預設值。
如果你為類定義了一個預設建構函式,又定義了一個所有參數都有預設的值的建構函式。(技術上來說,這是重載了)用預設建構函式構造類對象時將會產生編譯錯誤。因為編譯器不知道選擇哪個重載函數。
建構函式的初始化列表
除了在建構函式的函數體中用明確的賦值運算式給類成員賦值(從嚴格的概念上來說這不是初始化),推薦的做法是使用初始化列表。初始化列表以一個冒號開始,緊接著一個一個用逗號分隔的資料成員列表,每個資料成員後跟一個放在圓括弧中的初始化式。建構函式的初始化列表只能在實現中指定而不能在定義體中指定。而類的成員函數(建構函式也不例外)的實現既可以在類的定義體中(內嵌函式),也可以在類的實現中。
成員的初始化次序
每個成員只能在初始化列表中指定一次。而且成員在初始化列表中出現的順序並不代表成員的實際初始化順序。成員的初始化順序是按照它們在類定義中出現的順序來的。所以成員的初始化最好不要相互依賴,如果你確定它們要相互依賴,你得清楚它們在類定義中的出現順序。
建構函式的構造的兩個階段
(1)初始化階段(根據預設的變數初始化規則和初始化列表來執行);(2)建構函式中的函數體執行階段(這時建構函式體內的指派陳述式才會執行)。
為什麼推薦使用初始化列表?
1.在許多類中,初始化和賦值嚴格來講都是低效率的:資料成員可能已經被直接初始化了,還要對它進行初始化和賦值。
2.比第一點提到的效率更重要的是,某些類型的資料成員必須要初始化。
有些類型的成員必須在初始化列表中進行初始化,比如const對象和參考型別對象。它們只能初始化而不能賦值。在執行建構函式體之前必須完成初始化。在函數體內對它們賦值會引發編譯錯誤。
類類型的成員變數也要特別注意,如果你不對它在初始化列表中的初始化,編譯器將會嘗試在初始化階段調用它的預設建構函式給他初始化。如果它沒有預設的建構函式,這將會導致執行階段錯誤。另一種情況是你只在建構函式體內對類對象的成員進行了賦值。初始化階段將會調用該類對象成員的預設建構函式,計算階段將會調用建構函式體內指定建構函式。意思是該類對象成員調用了兩次建構函式,第二次的會覆蓋第一次的。
建構函式與隱式類型轉換、explicit
C++支援類型自動轉換。可以定義如何將其他類的對象隱式轉換為我們的類類型,也可以將我們的類類型對象隱式的轉換為其他類型。建構函式有個隱含規則:可以用單個實參類調用的建構函式定義了一個從該形參類型到該類類型的一個隱式轉換。有時候這不是你想要的,並且會引發錯誤。例如你定義了下面的類。
class Box{public:Box(int x=1,int y=1);int length;int width;};Box::Box(int x,int y):length(x),width(y){}
如果你Box box= 2來初始化一個Box對象。編譯器將2隱式轉換為一個Box對象,相當於調用了建構函式Box(2)。
如果你在需要Box型別參數的函數調用中傳入的是一個int實參,將會構造一個臨時的Box對象再傳入函數作參數。函數結束後,這Box對象也就消失了,這有什麼用呢?這幾乎肯定是一個錯誤。對此我們可以:
1.用關鍵字explicit阻止建構函式定義的隱式轉換
在類建構函式的聲明前加上explicit關鍵字(注意不能在定義中加),可以阻止隱式轉換。
class Box{public:explicit Box(int x=1,int y=1);int length;int width;};
如果你再這樣定義一個Box對象:Box box = 2或者將int類型對象作為參數當做Box對象傳給某個函數,將會引發編譯錯誤。
2.每次轉換,自己顯示的使用建構函式。這樣可以防止隱式轉換。
在需要Box對象實參的的函數調用中用func(Box(2))來調用類的建構函式建立一個臨時對象,防止自動的隱式轉換。
建議:除非有明確的理由允許隱式轉換,可以用單個參數調用的建構函式都應該定義為explicit。