從概念上講,可以認為構造函數分兩個階段進行:
1.初始化階段;
2.普通的計算階段。(計算階段由構造函數函數體中的所有語句組成)
不管成員是否在構造函數初始化列表中顯示初始化,類類型的數據成員總是在初始化階段初始化。初始化發生在計算階段的開始之前。
建議:使用構造函數初始化列表
注:必須對任何const或引用類型成員以及沒有默認構造函數的類類型的任何成員使用初始化式。
一般使用構造函數初始化列表,可以避免發生編譯錯誤。
講解:沒有默認構造函數的類?是什麼意思?
在大部分編譯器中,聲明一個類,若類沒有顯示的聲明和定義構造函數,那麼編譯器就會在編譯階段生成一個默認構造函數。如果用戶在該類中聲明了一個構造函數,那麼編譯器就不會生成默認構造函數,而是使用了用戶自己定義的構造函數,為了避免編譯錯誤,最好使用構造函數初始化列表對該類的對象進行初始化。
-----摘自於《C++ Primer 中文版 第4版》
類成員的初始化包括類對象成員與類數據成員的初始化。初始化比較關鍵的是構造函數的初始化列表,在構造函數中成員初始化列表中也需要次序的。只有構造函數才能有成員初始化的效果,普通的成員函數沒有這功能,比如:
[cpp] void CInit::setXY(int x, int y) : mX(x), mY(y)
{
}
void CInit::setXY(int x, int y) : mX(x), mY(y)
{
}這個初始化是錯誤的,setXY並非是構造函數,所以普通成員函數只能通過賦值的形式來設置變量或對象的值。
[cpp] void CInit::setXY(int x, int y)
{
mX = x;
mY = y;
}
void CInit::setXY(int x, int y)
{
mX = x;
mY = y;
}這裡聲明一個類CInit,如下:
[cpp] class CInit
{
public:
CInit(int x, int y);
void Show() const;
private:
int mX;
int mY;
};
void CInit::Show() const
{
cout << "mX = " << mX << endl
<< "mY = " << mY << endl;
}
class CInit
{
public:
CInit(int x, int y);
void Show() const;
private:
int mX;
int mY;
};
void CInit::Show() const
{
cout << "mX = " << mX << endl
<< "mY = " << mY << endl;
}
一.構造函數的初始化列表的基本使用
這是正常的初始化列表的用法
初始化:
[cpp] CInit::CInit(int x, int y) : mX(x), mY(y)
{
}
CInit::CInit(int x, int y) : mX(x), mY(y)
{
}達到的結果相當於
賦值:
[cpp] CInit::CInit(int x, int y)
{
mX = x;
mY = y;
}
CInit::CInit(int x, int y)
{
mX = x;
mY = y;
}
二.成員初始化的次序
在構造函數初始化列表中,成員初始化的次序就是聲明成員的次序。
例子1:張三想先用x初始化mX,再用mX初始化mY
[cpp] CInit::CInit(int x, int y) : mX(x), mY(mX)
{
}
CInit test(2, 3);
test.Show();
CInit::CInit(int x, int y) : mX(x), mY(mX)
{
}
CInit test(2, 3);
test.Show();此時的結果是:
[plain] mX = 2
mY = 2
mX = 2
mY = 2mX與mY均被成功的初始化。
例子2:李四想先初始化mY,再用mY初始化mX
[cpp] CInit::CInit(int x, int y) : mY(y), mX(mY)
{
}
CInit test(2, 3);
test.Show();
CInit::CInit(int x, int y) : mY(y), mX(mY)
{
}
CInit test(2, 3);
test.Show();此時的結果是:
[plain] mX = 2147344384 (不同機器可能不一致)
mY = 3
mX = 2147344384 (不同機器可能不一致)
mY = 3從結果可以很明顯的看出,mX沒有被初始化,而mY成功被初始化為3。
從這裡可以看出,構造函數是以變量的聲明順序來執行初始化的動作,所以例子2中,構造函數先初始化mX,但此時mY是未初始化過的,所以導致這種情況。在構造函數的初始化列表中,最好要按照類中成員變量的聲明順序來初始化。
三.在什麼情況下使用構造函數初始化列表?
1.const對象
2.引用類型對象
因為const對象與引用類型對象只能夠初始化,不能賦值,所以必須在初始化列表中進行初始化。
3.類對象(下文說明)
四.構造函數中,賦值初始化與初始化列表初始化,哪個效率更高?為什麼?
先來看一個例子,便可知分曉:
[cpp] #include <iostream>
using namespace std;
class CObj
{
public:
CObj()
{
cout << "調用默認構造函數" << endl;
}
CObj(const CObj &obj)
{
mX = obj.mX;
cout << "調用復制構造函數" << endl;
}
CObj & operator=(const CObj &obj)
{
if (&obj != this)
{
mX = obj.mX;
}
cout << "數據賦值" << endl;
return *this;
}
private:
int mX;
};
class CInit
{
public:
CInit(const CObj & obj) : mObj(obj)
{
//mObj = obj;
}
private:
CObj mObj;
};
int main()
{
CObj obj;
cout << endl;
CInit test(obj);
return 0;
}
#include <iostream>
using namespace std;
class CObj
{
public:
CObj()
{
cout << "調用默認構造函數" << endl;
}
CObj(const CObj &obj)
{
mX = obj.mX;
cout << "調用復制構造函數" << endl;
}
CObj & operator=(const CObj &obj)
{
if (&obj != this)
{
mX = obj.mX;
}
cout << "數據賦值" << endl;
return *this;
}
private:
int mX;
};
class CInit
{
public:
CInit(const CObj & obj) : mObj(obj)
{
//mObj = obj;
}
private:
CObj mObj;
};
int main()
{
CObj obj;
cout << endl;
CInit test(obj);
return 0;
}1.若CInit的構造函數為:
[cpp] CInit(const CObj & obj) : mObj(obj)
{
}
CInit(const CObj & obj) : mObj(obj)
{
}執行結果為:
[plain] 調用默認構造函數
調用復制構造函數
調用默認構造函數
調用復制構造函數在構造函數CInit的初始化列表初始化mObj對象時,調用了復制構造函數1次,總共需要1個行為。
2.若CInit的構造函數為:
[cpp] CInit(const CObj & obj)
{
mObj = obj;
}
CInit(const CObj & obj)
{
mObj = obj;
}執行結果為:
[plain] 調用默認構造函數
調用默認構造函數
數據賦值
調用默認構造函數
調用默認構造函數
數據賦值
在構造函數體中賦值mObj對象時,首先調用默認構造函數,其次是調用operator=賦值運算符,總共需要2個行為。
所以可以得出這麼一個結論:對於用戶自定義的類類型,使用構造函數初始化列表進行初始化的效率,比在構造函數體中賦值初始化的效率更高。對於內置類型,效率是差不多的。
五.構造函數初始化列表的使用例子
[cpp] /*類成員的初始化: 求2點組成的矩形*/
#include <iostream>
using namespace std;
class CCenterPoint
{
public:
CCenterPoint(int posX, int posY) : mPosX(posX), mPosY(posY)
{
}
void ShowPos() const
{
cout << "2點之間的中點坐標: (" << mPosX << "," << mPosY << ")" << endl;
}
private:
int mPosX;
int mPosY;
};
class CArea
{
public:
CArea(int length, int width) : mLength(length), mWidth(width)
{
}
void ShowArea() const
{
cout << "2點組成的矩形面積: " << mLength * mWidth << endl;
}
private:
int mLength;
int mWidth;
};
class CRect
{
public:
CRect(int posX1, int posY1, int posX2, int posY2)
: mPoint((posX1+posY1)/2, (posX2+posY2)/2),
mArea(posX2-posX1, posY2-posY1)
{
}
void Show() const
{
mPoint.ShowPos();
mArea.ShowArea();
}
private:
CCenterPoint mPoint;
CArea mArea;
};
int main()
{
CRect rect(10, 100, 20, 200);
rect.Show();
return 0;
}
/*類成員的初始化: 求2點組成的矩形*/
#include <iostream>
using namespace std;
class CCenterPoint
{
public:
CCenterPoint(int posX, int posY) : mPosX(posX), mPosY(posY)
{
}
void ShowPos() const
{
cout << "2點之間的中點坐標: (" << mPosX << "," << mPosY << ")" << endl;
}
private:
int mPosX;
int mPosY;
};
class CArea
{
public:
CArea(int length, int width) : mLength(length), mWidth(width)
{
}
void ShowArea() const
{
cout << "2點組成的矩形面積: " << mLength * mWidth << endl;
}
private:
int mLength;
int mWidth;
};
class CRect
{
public:
CRect(int posX1, int posY1, int posX2, int posY2)
: mPoint((posX1+posY1)/2, (posX2+posY2)/2),
mArea(posX2-posX1, posY2-posY1)
{
}
void Show() const
{
mPoint.ShowPos();
mArea.ShowArea();
}
private:
CCenterPoint mPoint;
CArea mArea;
};
int main()
{
CRect rect(10, 100, 20, 200);
rect.Show();
return 0;
}執行結果:
[plain] 2點之間的中點坐標: (55,110)
2點組成的矩形面積: 1000
2點之間的中點坐標: (55,110)
2點組成的矩形面積: 1000
Happy Learning!
摘自 gzshun的專欄