C++学习-构造函数初始化列表(4)

来源:岁月联盟 编辑:exp 时间:2012-02-16

从概念上讲,可以认为构造函数分两个阶段进行:
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的专栏