請註意以下繼承體系中各class的constructors寫法: 1 class CPoint 2 { 3 public: 4 CPoint(float x=0.0) 5 :_x(x){} 6 7 float x() {return _x;} 8 void x(float xval){_x=xval ...
請註意以下繼承體系中各class的constructors寫法:
1 class CPoint 2 { 3 public: 4 CPoint(float x=0.0) 5 :_x(x){} 6 7 float x() {return _x;} 8 void x(float xval){_x=xval;} 9 protected: 10 float _x; 11 }; 12 13 class CPoint2d:public CPoint{ 14 15 public: 16 CPoint2d(float x=0.0,float y=0.0) 17 :CPoint(x),_y(y){} 18 19 float y(){return _y;} 20 void y(float yval){_y=yval;} 21 protected: 22 float _y; 23 }; 24 25 class CPoint3d:public CPoint2d{ 26 public: 27 CPoint3d(float x=0.0,float y=0.0,float z=0.0) 28 :CPoint2d(x,y),_z(z){} 29 30 float z(){return _z;} 31 void z(float zval){_z=zval;} 32 33 protected: 34 float _z; 35 36 37 };
在constructor聲明之後有一個:符號,後面緊跟著一個(以上)的函數調用動作,這一行就是所謂的initialization list。它的作用是在進入constructor主體動作之前,,先喚起其中所列的函數。例如上面的:
第5行表示:在執行CPoint::CPoint(x)之前,先執行_x(x); (註:語言內建類型如int、float、long等等也是一種class,因為變數_x的類型是float,所以_x(x)的意思是啟動“float class”的constrnctor,也就把_x的初值設為x;
第17行表示:執行CPoint2d::CPoint2d(x,y)之前,先執行CPoint(x)和_y(y).
第28行表示:執行CPoint3d::CPoint3d(x,y,z)之前,先執行CPoint2d(x,y)和_z(z).
因此當我產生一個CPoint3d object如下:
CPoint3d aPoint3d(1.1, 2.2, 3.3);
會有以下六個動作依序被調用:
_x(1.1); // 相當於 _x = 1.1; CPoint::CPoint(1.1); // 本例沒做什麼事 _y(2.2); // 相當於 _y = 2.2; CPoint2d::CPoint2d(1.1, 2.2); // 本例沒做什麼事 _z(3.3); // 相當於_z = 3.3; CPoint3d::CPoint3d(1.1, 2.2, 3.3); // 本例沒做什麼事
你可能會問,既然繼承體系中的建構方式是由內而外,由上而下,那麼這裡產生個CPoint3d object,必然會調用CPoint2d和CPoint的constrnctor,而所有初始化動作都可以在其中完成,initialization list的出現會不會是顯得多此一舉?做個測試就知道了,把上一段27行的代碼改為這樣試試:
CPoint3d( float x = 0.0, float y = 0.0, float z = 0.0 ) { _z = z; }
其中沒有指定initialzation list。結果竟然無法通過編譯:
error C2668: 'CPoint2d::CPoint2d' : ambiguous call to overloaded function
也就是說,當編譯器根據繼承體系往上一層調用base class constructor時,發現CPoint2d有兩個constructors,而它不知道應該調用哪一個。這就是initialization list最明顯的存在的價值。如果本例的CPoint2d只有一個constructor,像這樣:
1 class CPoint2d : public CPoint { 2 public: 3 CPoint2d( ) { _y = 0.0; } // default constructor 4 protected: 5 float _y; 6 };
或者這樣
1 class CPoint2d : public CPoint { 2 public: 3 CPoint2d( float x = 0.0, float y = 0.0 ) 4 : CPoint( x ), _y( y ) { } 5 protected: 6 float _y; 7 };
而 CPoint3d constructor 中沒有列出 initialization list,像這樣:
1 class CPoint3d : public CPoint2d { 2 public: 3 CPoint3d( float x = 0.0, float y = 0.0, float z = 0.0 ) { _z = z; } 4 protected: 5 float _z; 6 };
那麼並不會出現前面的編譯錯誤。
以上的討論是針對base class的建構,同理對於member class 也是一樣。如果member calss有一個以上的constructors,那麼內含embedded object的那個class就必須在其constructor中指定initialization list,否則一樣會出現編譯錯誤。
initialization list到底會在編譯器底層發生什麼影響呢?編譯器會以“適當的次序”將initialization list中指定的member調用動作安插到constructor之內,並置於任何user code之前,下麵這張圖可以表現出編譯器的插碼結果:
有一些微妙的地方必須註意,編譯器安插在constructor中的members聲明動作是以members在class中的聲明次序為根據,而不是以initializtion list中的排序為根據。如果兩者在外觀上錯亂,很容易引起程式設計時的一些困擾或失誤。例如:
class X { public: X(int val) : m_data2(val), m_data1(m_data2) { } protected: int m_data1; int m_data2; };
我們很容易誤以為在X constructor中是以val 設定m_data2,再將m_data2設定給m_data1.但根據兩個data members的聲明順序,實際發生的動作卻是:
1 X::X(int val) 2 { 3 m_data1(m_data2); // 此時 m_data2 還沒有初值,糟糕 4 m_data2(val); 5 }
於是,當我們產生一個X object:
X x(3);
其實data members的內容可能成為這樣:
x.m_data1 = -2124198216 // 這不是我們希望的 x.m_data2 = 3
一個比較好的做法是,把class X重新設計如下:
1 class X { 2 public: 3 X(int val) : m_data2(val) { m_data1 = m_data2; } 4 protected: 5 int m_data1; 6 int m_data2; 7 };