先看两个例子,Demo1:
#include "stdafx.h"
#include <iostream>
using namespace std;
class StudentID{
public:
StudentID(){
cout<<"Default constructor "<<value<<endl;
}
StudentID(int id){
value =id;
cout<<"Assigning student id "<<value<<endl;
}
~StudentID(){
cout<<"Destructing id " << value <<endl;
}
protected:
int value;
};
class Student{
public:
Student(char * pName="noName",int ssID=0){
cout<<"Constructing student "<<pName<<endl;
strcpy_s(name,pName);
StudentID id(ssID);
}
protected:
char name[20];
StudentID id;
};
int _tmain(int argc, _TCHAR* argv[])
{
Student s("Randy",9818);
system("pause");
return 0;
}
输出结果:
Default constructor -858993460
Constructing student Randy
Assigning student id 9818
Destructing id 9818
Demo2:
#include "stdafx.h"
#include <iostream>
using namespace std;
class StudentID{
public:
StudentID(){
cout<<"Default constructor "<<value<<endl;
}
StudentID(int id){
value =id;
cout<<"Assigning student id "<<value<<endl;
}
~StudentID(){
cout<<"Destructing id " << value <<endl;
}
protected:
int value;
};
class Student{
public:
Student(char * pName="noName",int ssID=0):id(9818){
cout<<"Constructing student "<<pName<<endl;
strcpy_s(name,pName);
//StudentID id(ssID);
}
protected:
char name[20];
StudentID id;
};
int _tmain(int argc, _TCHAR* argv[])
{
Student s("Randy",9818);
system("pause");
return 0;
}
输出结果:
Assigning student id 9818
Constructing student Randy
Demo1和Demo2演示了类成员变量在构造函数体内与在初始化列表进行初始化时的不同,从中我们得到的结论是:
1.初始化列表的执行发生在进入构造函数函数体之前,它是在调用构造函数时进行初始化的。而对于函数体内的类成员变量赋值(非初始化,而是赋值),它是首先调用了类(StudentID)的默认构造函数来初始化成员变量,而后在函数体中调用赋值函数为类成员变量赋值,显然这个跟直接在初始化列表初始化相比默认构造函数的调用就显得有些多余,效率低了很多。
2.如果不显示的在初始化列表中进行初始化,默认情况下初始化列表是空白的给我们造成没有初始化列表的错觉,实际上在进入构造函数体内之前首先要经过初始化列表,那么对于类类型的成员则会在此期间调用了一次默认构造函数。即:类类型的数据成员对象在进入函数体前首先在成员初始化列表处调用默认构造函数进行构造对象的工作(这一步是必然存在的但对用户又是透明看不到的,因此为了阻击调用默认构造函数,我们可以在初始化列表中直接初始化),在进入函数体后,进行的是对已经构造好的类对象的赋值,又调用一次赋值操作符才能完成(如果并未提供,则使用编译器提供的默认赋值行为)。
除了类类型这种用户自定义的类型需要在初始化列表中进行初始化外,还有const成员和引用成员。
原因如下:我们知道,static成员属于类所有,该类的所有对象都共用一份static成员,而const 数据成员属于对象所有,不同的对象自己开辟空间来存储自己的const成员变量,即:const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象,其const数据成员的值可以不同,所以不能在类声明中初始化const数据成员;以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。
class A
{…
const int SIZE = 100; // 错误,企图在类声明中初始化 const 数据成员
};
既然const 数据成员不能在构造函数内进行初始化,那么const 数据成员只能在类构造函数的初始化表中进行:
class A
{…
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的初始化表
{
…
}
A a(100); // 对象 a 的 SIZE 值为 100
A b(200); // 对象 b 的 SIZE 值为 200
同理,引用类型和const一样,是属于类对象所有,而不是属于类的,也需要在初始化列表中完成初始化。
小结:
(1.)对非内置类型成员变量(如:类和string),为了避免两次构造,推荐使用类构造函数初始化列表。有的时候必须用初始化列表进行初始化:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化列表进行初始化,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败而报错。
2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
(2.)对内置类型成员变量和复合类型(如:指针),在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的.
说到这里,我们就不得不说static成员变量的初始化了,由于static表示的是静态的,类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的,即使没有具体对象,也能调用类的静态成员函数和成员变量,因此static的成员变量也不能在类的构造函数体内进行初始化,试想下如果能在构造函数体内初始化的话,那么就说明了static成员只有有了类的对象才能有值进行调用,这就违背了static成员属于类的原则了。即:static成员必须在类外进行初始化,而不能在构造函数内进行初始化。
<span style="color:#333333;">#include <iostream>
using namespace std;
class A{
public:
A()
{
//test = 1; //aa
}
static int test;
};
int A::test = 1; //bb
int main()
{
cout<<A::test<<endl;
return 1;
}</span>
小结:类的const成员、引用成员、static成员和类成员均不能在类的构造函数体内进行初始化。
Note:初始化列表的成员初始化顺序:
C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
Demo3:
#include "stdafx.h"
#include <iostream>
using namespace std;
class staticClass
{
public:
staticClass():iConst(0){}
private:
const int iConst;
static int iStatic;
};
int staticClass::iStatic = 3;
int _tmain(int argc, _TCHAR* argv[])
{
staticClass obj;
cout<<"sizeof(staticClass)="<<sizeof(staticClass)<<"\t"<<"sizeof(obj)="<<sizeof(obj)<<endl;
system("pause");
return 0;
}
输出的结果是两个4.
在main函数入口处、静态变量赋值处和类的构造函数处分别设置断点发现,在进入main函数前和构造函数调用前static就已经初始化完成;其次,类中的静态成员不对类的大小产生影响,因为静态变量的存储位置与类的实例地址无关。