考虑如下设计:
1 #include<iostream> 2 3 using namespace std; 4 5 class Entry 6 { 7 public: 8 Entry(const string& name, const string& address, const string& phone); 9 10 private: 11 string name; 12 string address; 13 string phone; 14 int nums; 15 }; 16 Entry::Entry(const string& name, const string& address, const string& phone) 17 { 18 this->name = name; // 构造函数内的这些语句都是赋值而不是初始化 19 this->address = address; 20 this->phone = phone; 21 this->nums = 0; 22 } 23 24 int main() 25 { 26 Entry entry("name: benxintuzi", "address: 123", "phones: 123456"); 27 28 return 0; 29 }
如上所述,在构造函数体内都为赋值语句,而不是初始化语句。C++规定,对象的成员变量的初始化时机发生在进入构造函数之前,这点非常重要。而对于基本变量类型如nums却不能保证进入构造函数之前将其初始化。上述语句的执行是:首先调用name/address/phone的default构造函数,然后在Entry的构造函数中对其进行赋值,如此一来,调用变量的默认构造函数就变得没有什么意义了,反正最终获得的值都会马上被换掉了。为了避免重复性的工作,利用变量的构造函数总会被调用的原则,将上述Entry的构造函数改进如下:
Entry::Entry(const string& name, const string& address, const string& phone) :
name(name), address(address), phone(phone), nums(0)
{ }
利用构造函数的初始化列表,虽然结果是相同的,但是效率会提高很多:因为初始化列表只会调用name等变量的copy构造函数,而不会再次调用Entry的赋值操作符函数了,这样Entry的构造函数体就可以做一些更有意义的事了。同样不指定实参的初始化列表同样有用,比如设计一个Entry的无参构造函数如下:
Entry::Entry() :
name(), address(), phone(), nums(0)
{ }
注意:
对于基本内置类型而言,在构造函数体内赋值与调用初始化列表指定初值,其代价是等同的。但是如果成员变量是const或者references,那么就没办法使用赋值操作了,必须使用初始化列表才能通过编译。所以我们要遵循的规则就是:总是使用初始化列表,这样做最差情况下是等效的,而一般情况下是高效的,甚至有时还是必须的。
C++类的成员初始化次序问题:
- 先基类后派生类。
- 类中成员变量的初始化次序依赖与声明次序,与初始化列表排列次序无关。
- 对于不同编译单元中(一个编译单元就是指能够产生单一目标文件的那些源码:包括源码文件、头文件展开、宏展开等构成的一个大文件)的变量引用问题,为了防止引用了一个还没有被初始化的变量(比如说某些资源句柄之类的),最好的办法就是将被引用的变量通过单例模式创建出来,这样就可以保证正确的初始化顺序了