記錄一下我對C++類構造函數的理解。
首先,構造函數分成兩種,默認構造函數和非默認構造函數(好吧,就這麼叫它)。
默認構造函數只能有一個,如果沒有自定義構造函數,那麼編譯器將自動生成一個默認構造函數,當然這個構造函數不會做任何事情。如果程序定義了構造函數(包括默認和非默認),編譯器都不再自動提供默認構造函數。
如class C, 對應自動生成的默認構造函數爲C() { };
程序員可以自定義默認構造函數,而且只能定義一個默認構造函數。如定義class A.
Class A
{
public:
// A() { } //constructor1, do nothing,默認構造函數
// A(int m=1):v1(m);//construct2,默認構造函數,提供了部分的默認初始化值
// A(int m=1, int n=2):v1(m), v2(n);//construct3, 默認構造函數,提供了所有的默認初始化值
// A(int m); //construct4,非默認構造函數, 提供一個參數
// A(int m, int n); //construct5, 非默認構造函數, 提供兩個參數
private:
int v1;
int v2;
}
下面具體分析一下各種定義方式會帶來的問題:
1)不定義任何構造函數,這個上面說過,編譯器會自動構造一個默認構造函數,等價於定義了construct1。
2) 只定義construct2, 可以正確編譯運行。但是無法以形式A a(1,2)定義對象,這是顯然的。
3) 只定義construct3, 可以正確編譯運行。
4) 定義2個及以上默認構造函數的情況。
想來同時提供construct2和construct3是不會矛盾的,而且符合重載的要求,但是事實並非如此。
同時定義construct2和construct3, 以形式 A a;定義對象將會導致編譯器報錯。
error: call of overloaded ‘A()’ is ambiguous
note: candidates are: A::A(int, int)
note: A::A(int)
以形式A a(1);定義對象將會導致報錯:
error: call of overloaded ‘A(int)’ is ambiguousnote: candidates are: A::A(int, int)
note: A::A(int)
note: A::A(const A&)
然而,以形式 A a(1,2);定義對象則可以正確通過編譯並運行。 這是什麼原因?編譯器工作的原理是什麼?不知道....
5) 從重載的角度分析,construct2和construct3簽名一致,construct4和construct5簽名一致,不能同時定義。實驗結果證實確實如此。
6) construct4和5恰爲構造函數重載的典型實例,當然能正確編譯運行。
7 ) 只提供非默認構造函數,只有construct4或5或同時有4和5而無1/2/3
這將導致無法使用 A a;的形式來聲明對象。因爲以A a的形式聲明對象是調用默認構造函數來構造對象的。編譯器找不到默認構造函數自然會報錯。
與此同時對象數組的聲明A a[10];也將得到報錯,原理同上。
那麼如下聲明並初始化對象數組會是什麼結果呢?
A a[3] = {
A(1,1), A(2,2), A(3,3)
} //(再只定義了construct5的情況下)
在C++ Primer Plus中有如下語句:“要創建類對象數組,則這個類必須有默認構造函數。”,“初始化對象數組的方案位,首先使用默認構造函數創建數組元素,然後花括號中的構造函數將創建臨時對象,然後將臨時對象的內容複製到相應的元素中。因此要創建類對象數組,則這個類必須有默認構造函數。”
在我看來這話不盡然正確,或許Stephen寫書的時候使用的編譯器是如他所說,但是我在g++中做的實現不是如此。上述對象數組的定義在g++下能夠通過編譯,並運行正確。g++的策略是,如果花括號中明確調用了構造函數來初始化對象,且該構造函數已被定義,那麼將直接使用構造函數來創建對象元素,無臨時對象和複製的過程。然而,如果花括號中有任一元素沒有顯示的使用已定義的構造函數初始化,那麼編譯器將調用默認構造函數來初始化該對象元素,這時如果沒有定義默認構造函數,將無法通過編譯。
那麼在上述情形中 A a[4] = { A(1,1), A(2,2), A(3,3)}, 將無法通過編譯,因爲A[3]實際上要通過調用默認構造函數來初始化,而這裏又沒有定義默認構造函數。
關於默認構造函數的調用。
實際上在g++中,未提供任何構造函數時,初始化一個對象,編譯器並不去調用自動提供的那個什麼也不做的構造函數,而僅僅是在內存中爲對象分配了空間。(這應該是編譯器的優化手段,不同編譯器不同。)如果程序自己定義了一個A(){};雖然也什麼都不做,但是初始化對象時,該構造函數卻會被執行。這就帶來了額外的開銷,所以如果真的不定義任何構造函數的話,那麼就乾脆也別定義A(){};這麼個空構造函數了,無意義,反而降低效率。
構造函數的參數傳遞,參數的第一個值爲隱藏的this指針,指向對象的首地址,其次纔是傳進去的參數。
類對象的大小:無任何屬性域的對象,大小爲1, 即類class A{}; 有屬性域的類,大小爲屬性大小之和。this指針不佔內存。