类成员的初始化——初始化列表
在C++语言中,构造函数被赋予了一定的功能:对类成员变量完成初始化赋值操作。这一步是类生命过程中十分重要。打个比方说,如果说应用程序从操作系统中申请内存就如同地产商从政府手中拍得一块土地,那么类成员的初始化就是建筑商在这块竞拍得到的土地中建起一栋未经装饰的公寓。杂草丛生的土地经过建设具备了商品房的基本形态,而空空如也的 raw 内存经过初始化就被赋予了对象的生命气息。
类成员的初始化可以采用如下两种形式来完成:
1.在构造函数中进行赋值完成
2.用初始化成员列表完成
首先我们假设声明如下:
class CStudent { public: CStudent(string name,int age,int id); virtual ~CStudent(); private: string m_name; int m_age; int m_id; };
下面是构造函数体中赋值完成。
CStudent::CStudent(string name,int age,int id) { m_name = name; m_age = age; m_id = id; }
下面是用类初始化列表来完成
CStudent::CStudent(string name,int age,int id) :m_name(name) ,m_age(age) ,m_id(id) {}
从功能上来讲,两种方法都可以。但是二者的实现细节稍有不同,第一种在构造函数体内实现的“=”操作的本质是赋值(Assign)操作,而第二种才是真正的初始化(Initialization)。虽然两种方式一般情况下都适用,但是有些情况却只能使用第二种,比如:
class A { public: A(int data = 0); ~A(); private: int m_data; const int DATA_TYPE; }; A::A(int data) { m_data = data; DATA_TYPE = 0; }
这段代码在VS2012中 IntelliSense 提示表达式必须是可修改的左值,同时还会提示 DATA_TYPE 未初始化,所以在这种情况下,就只能采用初始化列表来初始化
A::A(int data):DATA_TYPE(0) { m_data = data; //最好也放入初始化成员列表 }
class A { private: A operator = (const A& rhs); }; class B { public: B(); ~B(); private: A m_A; };
此时要想在 B 的构造函数中调用 A 的赋值函数已经不可能了,已经无法采用构造函数进行初始化,代码如下:
B::B() { //error 无法访问 private 成员 m_A = A(); }
解决的办法就是采用初始化列表方式,代码如下:
B::B():m_A(A())
{
}
即使没有禁用赋值操作,还是不推荐采用构造函数体内的赋值初始化方式。因为这种方式存在两个问题,第一,比起初始化列表效率偏低;第二,留有错误隐患。
假设类 B 中含有一个 A 类型的成员变量:
class A { }; class B { public: B(); ~B(); private: A m_A; }; B::B() { m_A = A(); }
在这个过程中,会产生临时对象,临时对象的构造和析构会造成无谓的效率损耗,而初始化列表的方式就避免了产生临时对象所带来的问题。其实这就是初始化和赋值的区别。关于它们在效率上的差异,我们通过下面的实验来验证:
#include<iostream> using namespace std; #include<ctime> class CStudent { public: CStudent(string name,int age,int id); ~CStudent(); private: string m_name; int m_age; int m_id; }; CStudent::CStudent(string name,int age,int id) { m_name = name; m_age = age; m_id = id; } CStudent::~CStudent(){}; class CStudentA { public: CStudentA(string name,int age,int id); ~CStudentA(); private: string m_name; int m_age; int m_id; }; CStudentA::CStudentA(string name,int age,int id) { m_name = name; m_age = age; m_id = id; } CStudentA::~CStudentA(){}; int main() { clock_t startTime = 0,endTime = 0; startTime = clock(); for(int i = 0;i < 50000;i++) { CStudent student("Li Lei",21,2011001); } endTime = clock(); cout<<"采用赋值初始化 Time = "; cout<<endTime - startTime<<endl; startTime = clock(); for(int i = 0;i < 50000;i++) { CStudentA student("LI LEI",22,2012001); } endTime = clock(); cout<<"采用初始化列表初始化 Time = "; cout<<endTime - startTime<<endl; return 0; }
在Intel Core™ i3 m330 CPU多次实验上得出的结果如下
另外,之所以说它留有隐患,可能引发问题,是因为C++会悄悄的给我们加上默认的赋值函数,对于千变万化的类而言,默认的赋值函数操作很可能存在问题。如果你没有在意,即使没有重写,也没有禁止,那么它极有可能在其他类的函数中引发问题。
对于初始化列表,还有一个问题要说明,那就是他的初始化顺序。与构造函数中的赋值方式不同,初始化列表中的成员变量出现的顺序并不是真正的初始化顺序,初始化的顺序取决于成员变量在类中的声明顺序。例如:
class C { public : int m_num; string m_str; double* m_p; C():m_str("Hello"),m_p(NULL),m_num(0){} };
其初始化顺序是:m_num –> m_str –> m_p。
需要注意的是,只有保证成员变量声明顺序和初始化列表一致才能真正保证其效率。
说了这么多,虽然初始化列表有诸多优势,但是赋值初始化并不是一无是处,如果遇到了一下情况,采用赋值初始化也许更加便利:
class D { public: D(int data); ~D(){}; private: int m_a; int m_b; int m_c; int m_d; int m_e; }; //Initiation_List Version D::D(int data):m_a(data),m_b(data),m_c(data),m_d(data),m_e(data) {} //Assignment Version D::D(int data) { m_a = m_b = m_c = m_d = m_e = data; }
请记住:
在 Class 中成员变量初始化是一个不可或缺的步骤;在初始化成员变量时,出于对方法的通用性及高效性的考虑,强烈推荐采用成员变量初始化列表的方式初始化变量,并且要保证成员变量声明的顺序和初始化列表的顺序一致