c++初始化列表和直接赋值的区别

什么是构造函数初始化列表

定义两个构造函数:

Sales_data(const std::string &s): bookNo(s) {}
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }

这两个定义中,冒号以及冒号和花括号之间的代码,其中花括号定义了(空的)函数体。我们把这部分称为构造函数初始化列表,它负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来(或者在花括号内的)成员初始值。不同成员的初始化通过逗号分隔开来。

含有三个参数的构造函数分别使用它的前两个参数初始化成员 bookNo 和 units_sold, revenue的初始值则通过将售出图书总数和每本书单价相乘计算得到。

只有一个 string 类型的参数的构造函数使用这个 string 对象初始化bookNo,对于 units_sold 和 revenue 则没有显式地初始化。当某个数据成员被构造函数初始值列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化。在此例中,这样的成员使用类内初始值初始化,因此只接受一个 string 参数的构造函数等价于

// 与上面定义的那个构造函数效果相同
Sales_data (const std::string &s) :
            bookNo(s), units_sold(0), revenue(0) { }

通常情况下,构造函数使用类内初始值不失为一种好的选择,因为只要这样的初始值存在我们就能确保为成员赋予了一个正确的值。不过,如果你的编译器不支持类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。

注意:构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同。如果你不能使用类内初始值,则所有构造函数都应该显式地初始化每个内置类型地成员。

在上面的两个构造函数中函数体都是空的。这是因为这些构造函数的唯一目的就是为数据成员赋初值,一旦没有其他任务需要执行,函数体也就为空了。


构造函数初始化列表和赋值的不同

当我们定义变量时习惯于立即对其进行初始化,而非先定义、再赋值:

string foo = "Hello World!";    // 定义并初始化

string bar;                     // 默认初始化成空 string 对象
bar = "Hello World!";           // 为bar赋一个新值

就对象的数据成员而言,初始化和赋值也有类似的区别。如果没有在构造函数的初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。例如:

// Sales_data构造函数的一种写法,虽然合法但比较草率:没有使用构造函数初始值
Sales_data::Sales_data(const string &s,unsigned cnt, double price)
{
    bookNo = s;
    units_sold = cnt;
    revenue = cnt*price;
}

这段代码和上面构造函数的原始定义效果是相同的:当构造函数完成后,数据成员的值相同。区别是原来的版本初始化了它的数据成员,而这个版本是对数据成员执行了赋值操作。这一区别到底会有什么深层次的影响完全依赖于数据成员类型。

构造函数的初始值有时必不可少

有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。如果成员是 const 或者是引用的话,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。例如:

class ConstRef{
public:
     ConstRef(int ii);
private:
     int i;
     const int ci;
     int &ri;
};

和其他常量对象或者引用一样,成员 ci 和 ri 都必须被初始化。因此,如果我们没有为它们提供构造函数初始值的话将引发错误:

//错误:ci 和 ri 必须被初始化

ConstRef::ConstRef(int ii)
{  //赋值:
     i = ii;        //正确
     ci = ii;       //错误:不能给 const 赋值
     ri = i;        //错误,ri 没被初始化
}

随着构造体函数一开始执行,初始化就完成了。我们初始化 const 或者引用类型的数据成员的唯一机会就是通过构造函数初始值,因此该构造函数的正确形式应该是:

//正确:显式地初始化引用和const成员
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) {}

建议:

在很多类中,初始化和赋值的区别事关底层效率问题:前者直接初始化数据成员,后者则先初始化再赋值。

除了效率问题外更重要的是,一些数据成员必须被初始化。所以建议养成使用构造函数初始值的习惯,这样能避免某些意想不到的编译错误,特别是遇到有的类含有需要构造函数初始值的成员时。

你可能感兴趣的:(c++初始化列表和直接赋值的区别)