int i = 1;
class MyCls {
public:
MyCls():
m_nFor(m_nThd),
m_nSec(i++),
m_nFir(i++),
m_nThd(i++) {
m_nThd=i;
}
void echo() {
cout<< "result: " << m_nFir+m_nFor+m_nSec+m_nThd << endl;
}
private:
int m_nFir;
int m_nSec;
int m_nThd;
int &m_nFor;
};
int main()
{
MyCls oCls;
oCls.echo();
return 0;
}
首先給出答案:11.
該題涉及的知識點:構造函數初始值列表,複合類型
- 構造函數初始值列表
成員初始化順序說明
構造函數初始值列表只說明用於初始化成員的值,而不限定初始化的具體執行順序。
成員的初始化順序與它們在定義中的出現順序一致:第一個成員先被初始化,然後第二個,以此類推。構造函數初始值列表中的初始值的前後位置關係不會影響實際的初始化順序。
一般來說,初始化的順序沒什麼特別要求。不過如果一個成員是用另一個成員來初始化的,那麼這兩個成員的初始化順序就很關鍵了。
例如:
class X { int i; int j; public: X(int val):j(val), i(j) { } };
此例中,從構造函數初始值的形式上來看彷彿是先用val初始化了j,然後再用j初始化i。實際上,i先被初始化,因此這個初始值的效果是試圖使用未定義的值j初始化i!
有的編譯器具備一項比較友好的功能,即當構造函數初始值列表中的數據成員順序與這些成員的聲明順序不符時會生成一條警告信息。
-----<<C++ Primer>>
結合上述,即初始化的順序是m_nFir,m_nSec,m_nThd,&m_nFor。
- 複合類型
複合類型是基於其他類型定義的類型。C++語言有幾種複合類型,如引用和指針。下面講解引用。
- 引用
C++11中新增了一種引用:所謂的"右值引用",主要用於內置類。嚴格來說,當我們使用術語"引用"時,指的其實是"左值引用"。
引用(reference)爲對象起了另外一個名字,引用類型引用另外一種類型。通過將聲明符寫成&d的形式來定義應用類型,其中d是聲明的變量名:
int ival = 1024; int &refval = val; //refval指向ival(是ival的另一個名字) int &refval2; //報錯:引用必須被初始化
一般在初始化變量時,初始值會被拷貝到新建的對象中。然而定義引用時,程序把引用和它的初始值綁定在一起,而不是將初始值拷貝給引用。一旦初始化完成,引用將和它的初始值對象一直綁定在一起。因爲無法令引用重新綁定到另外一個對象,因此引用必須初始化。
☆引用即別名
引用並非對象,相反的,它只是爲一個已經存在的對象所起的另外一個名字。
定義了一個引用後,對其進行的所有操作都是在與之綁定的對象上進行的。
refVal = 2; //把2賦給refVal指向的對象,此處即是賦給了ival int ii = refval; //與ii = ival執行結果一樣
爲引用賦值,實際上是把值賦給了與引用綁定的對象。獲取引用的值,實際上是獲取了與引用綁定的對象的值。同理,以引用爲初始值,實際上是以與引用綁定的對象作爲初始值:
int &refVal3 = refVal; //正確:refVal3綁定到了那個與refVal綁定的喜愛那個上,這裏就是綁定到ival上 //利用與refVal綁定的對象的值初始化變量i int i = refVal; //正確:i被初始化爲ival的值
因爲引用本身不是一個對象,所以不能定義引用的引用。
☆引用的定義
允許在一條語句中定義多個引用,其中每個引用標識符都必須以符號&開頭:
int i = 1024,i2 = 2048; //i和i2都是int int &r = i, r2 = i2; //r是一個引用,與i綁定在一起,r2是int int i3 = 1024, &ri = i3; //i3是int,ri是一個引用,與i3綁定在一起 int &r3 = i3, &r4 = i2; //r3和r4都是引用
除了兩種特殊情況:①初始化常量引用時允許用任意表達式作爲初始值,只要該表達式的結果能轉換成引用的類型即可。尤其,允許爲一個常量引用綁定非常量的對象、字面值,甚至是一個一般表達式:
int i = 42; const int &r1 = i; //允許將const int&綁定到一個普通int對象上 const int &r2 = 42; //正確:r1是一個常量引用 const int &r3 = r1 * 2; //正確:r3是一個常量引用 int &r4 = r1*2; //錯誤:r4是一個普通的非常量引用
要理解這種例外情況的原因,最簡單的辦法是弄清楚當一個常量引用綁定到另外一種類型時發生了什麼:
double dval = 3.14; const int &ri = dval;
此處ri引用了一個int型的數。對ri的操作應該是整數運算,但dval卻是一個雙精度浮點數而非整數。因此爲了確保ri綁定到一個整數,編譯器把上述代碼變成了如下形式:
const int temp = dval; //由雙精度浮點數生成一個臨時的整型常量 const int &ri = temp; //讓ri綁定這個臨時量
在這種情況下,ri綁定了一個臨時量(temporary)對象。所謂臨時量對象就是當編譯器需要一個空間來暫存表達式的求值結果時臨時創建的一個未命名的對象。C++程序員們通常把臨時量對象簡稱爲臨時量。
當ri不是常量時,如果執行了類似於上面的初始化過程將帶來什麼後果呢?如果ri不是常量,就允許對ri賦值,這樣就會改變ri所引用對象的值。注意,此時綁定的對象是一個臨時量而非dval,既然想讓ri引用dval,就肯定想通過ri改變dval的值,否則給ri賦值就沒有意義了。如此看來,既然大家基本上不會想着把引用綁定到臨時量上,C++語言也就把這種行爲歸爲非法。② 存在繼承關係的類是一個重要的例外:可以將基類的指針或引用綁定到派生對象上。這有一層極爲重要的含義:當使用基類的引用(或指針)時,實際上並不清楚該引用(或指針)所綁定對象的真實類型。該對象可能是基類的對象,也可能是派生類的對象。
除了以上兩種例外情況,其他所有引用的類型都要和與之綁定的對象嚴格匹配。而且,引用只能綁定在對象上,而不能與字面值或 某個表達式的計算結果綁定在一起:
int &refVal = 10; //錯誤:引用類型的初始值必須是一個對象 double dval = 3.14; int &refVal5 = dval; //錯誤:此處引用類型的初始值必須是int型對象
-----<<C++ Primer>>
結合以上分析,即是說聲明的m_nFor引用的對象是m_nThd,也即同一地址的值。
綜上所述,對於此代碼段:
int i = 1;
class MyCls {
public:
MyCls():
m_nFor(m_nThd), //第④步
m_nSec(i++), //第②步
m_nFir(i++), //第①步
m_nThd(i++)/*第③步*/ {
m_nThd=i; //第⑤步
}
void echo() {
cout<< "result: " << m_nFir+m_nFor+m_nSec+m_nThd << endl;
}
private:
int m_nFir;
int m_nSec;
int m_nThd;
int &m_nFor;
};
int main()
{
MyCls oCls;
oCls.echo();
return 0;
}
構造函數中的初始化順序爲m_nFir、m_nSec、m_nThd、m_nFor,又m_nFor是m_nThd的引用。所以m_nFir被初始化爲1,m_nSec爲初始化爲2,m_nThd爲3,m_nFor也爲3,此時i = 4,執行構造函數體內的代碼,m_nThd = i = 4,m_nFor 爲m_nThd的引用,也爲4。所以輸出結果爲:1 + 4 + 2 + 4 = 11。