C++初始化列表详解

目录
        定义
        使用初始化列表的原因
        必须使用初始化列表的时候
        成员变量的顺序

定义

与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
private:
    int _year;
    int _month;
    int _day;
};

从概念上来讲,构造函数的执行可以分成两个阶段:初始化阶段和计算阶段,初始化阶段先于计算阶段。

初始化阶段
所有类类型(class Type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。

计算阶段
一般用于执行构造函数体内的赋值操作。

下面的代码中,其中 Date 有构造函数,拷贝构造函数及赋值运算符,为的是方便查看结果。Time 是个测试类,它以 Date 的对象为成员,我们看一下 Time 的构造函数是怎么样执行的。

class Date
{
public:
    Date() // 无参构造函数
    {
        cout << "Date()" << endl;
    }

    Date(const Date& d) // 拷贝构造函数
    {
        cout << "Date(const Date& d)" << endl;
        _year = d._year;
    }

    Date& operator=(const Date& d) // 赋值运算符
    {
        if (this != &d)
        {
            cout << "Date& operator=(const Date& d)" << endl;
            _year = d._year;
            return *this;
        }
    }
private:
    int _year;
};

class Time
{
public:
    Time(Date& d)
    {
        _d = d;
    }
private:
    Date _d;
};

测试用例:

void Test()
{
    Date d1;
    Time t1(d1);
}

输出结果:
在这里插入图片描述
解释说明:
第一行输出对应测试用例中的第一行,构造一个Date对象
第二行输出对应Time构造函数中的代码,此时会先调用Date类的默认构造函数初始化对象 _d,即所谓的初始化阶段。
第三行输出对应Time的赋值运算符,对 _d 执行赋值操作,即所谓的计算阶段。

使用初始化列表的原因

初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。
主要是性能问题,对于内置类型,如int, double等,使用初始化列表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由下面的测试可知,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。同样看上面的例子,我们使用初始化列表来实现Time的构造函数。

class Time
{
public:
    Time(Date& d)
        : _d(d)
    {}
private:
    Date _d;
};

使用同样的测试用例,输出结果如下:
在这里插入图片描述
解释说明:
第一行输出对应测试用例中的第一行,构造一个Date对象。
第二行输出对应Time的初始化列表,直接调用拷贝构造函数初始化 _d,省去了调用默认构造函数的过程。
所以一个好的原则是,能使用初始化列表的时候尽量使用初始化列表。

必须使用初始化列表的时候

除了性能问题之外,有些时候初始化列表是不可或缺的,以下几种情况时必须使用初始化列表
1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化

class Date
{
public:
    Date(int year)
        : _year(year)
    {}
private:
    int _year;
};

class Time
{
public:
    Time(Date& d)
    {
        _d = d;
    }
private:
    Date _d;
};

以上代码无法通过编译,因为Time的构造函数中 _d = d这一行实际上分成两步执行:
首先调用Date的默认构造函数来初始化_d
由于Date没有默认的构造函数,所以无法执行初始化_d,故而编译错误。正确的代码如下,使用初始化列表代替赋值操作

class Time
{
public:
    Time(Date& d)
        : _d(d)
    {}
private:
    Date _d;
};

成员变量的顺序

成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的,如下代码:

class Fun
{
public:
    //先初始化_x,再初始化_y
    Fun(int tmp)
        : _x(tmp)
        , _y(_x)
    {}    
private:
    int _x;
    int _y;
};

再看下面的代码:

class Fun
{
public:
    //_y未定义
    Fun(int tmp)
        : _y(tmp)
        , _x(_y)
    {}    
private:
    int _x;
    int _y;
};

这里 _x 的值是未定义的,虽然 _y 在初始化列表里面出现在 _x 前面,但是 _x 先于 _y 定义,所以必须先初始化 _x,而 _x 由 _y 初始化,此时 _y 尚未初始化,所以导致 _x 的值未定义。一个好的习惯是,按照成员定义的顺序进行初始化 。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值