C++中类的初始化操作有四个部分组成:

1.初始化列表:所有类非静态数据成员都可以在这里初始化,
所有类静态数据成员都不能在这里初始化(static 属于类,为类的所有成员共有,所以一般在类的内部声明,类的外部初始化)

2.构造函数体:
对于类非静态数据成员:
const型成员不能在这里初始化
引用型成员不能在这里初始化
没有默认构造函数的成员不能在这里初始化
对于类静态数据成员:
可以在这里修改可修改的静态成员,但静态成员必须已经在类外部初始化

3.类外初始化:除一个特例外,所有类static数据成员必须在这里初始化,
特例是类static const int数据成员可以在这里初始化,也可以在成员的声明处初始化

4.类中声明时直接赋值:类static const int数据成员可以选在这里初始化。

让我们从一段简单的代码开始:

class A { 
 const int x; 
 public: 
 A() { 
 this->x = 1; /* Error! */ //在构造函数内部初始化 
 } 
 };


对很多人而言,这是什么直观写法,为什么就错了呢?其实这本质上相当于写:

const int x; //const 修饰符 定义时即需要初始化 
 x = 1;


所以我们只能按如下方式声明其初始化:

class A { 
 const int x; 
 public: 
 A() : x(1) { //利用初始列表初始化 
 } 
 };


再来看一段简单的代码:

class A { 
 int &x; 
 public: 
 A(int k) { 
 this->x = k; /* Error! */ 
 } 
 };


同理这这本质上相当于写:

int &x; 
 x = k;


所以我们只能按如下方式声明其初始化:

class A { 
 const int x; 
 public: 
 A(int k) : x(k) { 
 } 
 };

有了上面两个简单例子作为引子,我们开始进一步讨论C++初始化的全过程。

其实我相信很多人还是怀着这样一些疑问“写在初始化列表里就相当于int &x=k;吗?”
且让我们来看看C++类的初始化的全过程:

(1)静态成员初始化阶段:所有类的静态成员应该都是在这个阶段初始化的。
注意初始化的顺序,就是操作语句的顺序,例如你有一个Test类:

int Test::x = 2; 
 int Test::y = 3;


需要注意的是2点,一是初始化语句不再需要static关键字,二是执行顺序就是语句的顺序,这里是先初始化x,再初始化y。执行顺序的问题在静态成员是类的时候就关系到构造函数的调用顺序了。另外需要注意的是,这些静态成员的初始化在任何具体实例被创建前就已经完成了。

(2)实例初始化列表工作阶段:
需要说的是,在用户使用new或者其他方法开始构建实例的时候,第一步首先是向操作系统申请内存,初始化列表是在申请成功后才开始工作的。然后,根据非静态成员的声明顺序开始执行如下操作:
1.如果该成员没有出现在初始化列表中:
1)如果是内置非const且非引用类型,不设定初值
2)如果是const类型,报错,必须在这里给定初值
3)如果是引用类型,报错,必须在这里给定初值
4)如果是class类型,就调用默认构造函数,进行初始化操作
2.如果该成员出现在初始化列表中:
1)如果是内置类型,就按初始化列表指定的值设定初值
2)如果是const类型,就按初始化列表指定的值设定初值
3)如果是引用类型,就按初始化列表指定的值设定初值
4)如果是class类型,就调用初始化列表指定的构造函数进行初始化操作
补充:构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数(构造函数内部初始化数据)是对类的成员赋值,并没有进行显式的初始化。
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。

初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:
**1.内置数据类型,复合类型(指针,引用)
在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
2.用户定义类型(类类型)
结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)(初始化列表一次构造,而在类内部会出现两次构造)**

(3)计算阶段:
根据构造函数的函数体进行赋值操作,或者修改操作,在这里,静态和非静态数据都可以赋值和修改
下面用一段代码来测试这个过程:

class Test1 { /用于测试Test2中含有没有默认构造函数的成员时的情况/ 
 public: 
 int i; 
 Test1(int a): i(a){} /这就取消了Test1的默认构造函数/ //自定义构造函数 
 }; class Test2 { 
 public: 
 int a; //int a = 1;Error:不允许数据成员初始值设定项 
 const int b; 
 static int c; 
 static const int d = 4;//正确,这样赋值也是可以的,也可以选在类声明外进行赋值 
 //但是如果不赋值,则程序中没有使用d不出错,使用了就会有link error //无法解析的外部命令 
 //static const float ff = 4.0; Error:只有静态常量整形数据成员才可以在类中初始化 
 int &e; 
 const int &f; 
 static int &g; 
 static const int &h; 
 //static const int &h = x_h; Error:只有静态常量整形数据成员才可以在类中初始化 
 Test1 t1; 
 const Test1 t2; 
 static Test1 t3; 
 const static Test1 t4; 
 Test2(int b, int e, int f, Test1 t1, Test1 t2) 
 : b(b), 
 //d(4), Error: d不是类的非静态成员或基类 
 e(e),//如果没有这句,Error:Test2:e没有提供初始化值 
 f(f), 
 t1(t1),//如果没有这句,Error:Test1没有默认构造函数 
 t2(t2) 
 { 
 a = 1; 
 //b = 2; //Error:表达式必须是可修改的左值,b是左值,不能修改 
 c = 3; 
 //d = 4; //Error:表达式必须是可修改的左值,d是左值,但不能修改 
 } 
 } 
 //int Test2::a = 1; //Error:非静态数据成员不能在其类的外部定义 
 //int Test2::b = 2; //Error:非静态数据成员不能在其类的外部定义 
 int Test2::c = 3; //如果没有这句,会出现无法解析的外部符号public:static int A::c 
 //int Test2::d = 4; //Error: int与声明const int不兼容 
 //int const Test2::d = 4; //和在类声明里面直接写赋值等价 
 int x_g = 5; /这个全局变量主要用户后续的静态成员赋值/ 
 int x_h = 6; /这个全局变量主要用户后续的静态成员赋值/ 
 Test1 x_t3(7);/这个全局变量主要用户后续的静态成员赋值/ 
 Test1 x_t4(8);/这个全局变量主要用户后续的静态成员赋值/ 
 int& Test2::g = x_g; 
 const int& Test2::h = x_h; 
 Test1 Test2::t3 = x_t3; 
 const Test1 Test2::t4 = x_t4;


前面讲了这么多具体的细节,我个人建议按如下简化规则来记忆
(1)所有static成员变量在类外初始化(不管它是const,是引用,还是没默认构造函数的对象)
(2)普通成员变量,是const,是引用,是没默认构造函数的,必须在初始化列表初始化
(3)普通成员变量,需要复杂运算的初始化变量,应该在构造函数内初始化,否则尽量在初始化列表中初始化。

另外补充2个小点:
(1)初始化列表的使用可能提高性能

class Test3 { 
 public: 
 int a; 
 Test3() { 
 a = 0; 
 puts(“Test3 constructor”); 
 } 
 Test3(Test3 &t3) { 
 this->a = t3.a; 
 puts(“Test3 copy constructor”); 
 } 
 Test3& operator=(Test3 &t) { 
 puts(“Test3 assign operator”); 
 this->a = t.a; 
 return *this; 
 } 
 ~Test3() { } 
 }; class Test4 { 
 public: 
 Test3 t3; 
 //Test4( Test3 &t3) : t3(t3) { //这种方式和下面的方式有相同的效果,不同的效率 
 //} 
 Test4( Test3 &t3) { 
 this->t3 = t3; 
 }; 
 };


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

struct foo 
 { 
 int i ; 
 int j ; 
 foo(int x):i(x), j(i){}; // ok, 先初始化i,后初始化j 
 };


再看下面的代码

struct foo 
 { 
 int i ; 
 int j ; 
 foo(int x):j(x), i(j){} // i值未定义 
 };


这里i的值是未定义的因为虽然j在初始化列表里面出现在i前面,但是i先于j定义,所以先初始化i,但i由j初始化,此时j尚未初始化,所以导致i的值未定义。所以,一个好的习惯是,按照成员定义的顺序进行初始化。也就是说相当于实际执行顺序是:

i(j); 
 j(x);


所以会出现错误。