在我们创建类对象的时候,类对象的构造顺序如下:
- 分配内存,调用构造函数时,隐式/显示的初始化各数据成员;
- 进入构造函数后在构造函数中执行一般赋值与计算。
使用初始化列表的情况有三种:
- 需要初始化的数据成员是对象的情况
- 需要初始化const修饰的类成员以引用类型的成员数据
- 子类需要初始化父类的私有成员
-
情况一的说明:数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数;
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。
#include "iostream" using namespace std; class Test { public: Test (int, int, int){ cout <<"Test" << endl; }; private: int x; int y; int z; }; class Mytest { public: Mytest():test(1,2,3){ //初始化 cout << "Mytest" << endl; }; private: Test test; //声明 }; int main() { Mytest test; return 0; }
输出结果
如果没有 mytest():test(1,2,3){}
初始化列表就会报错
因为Test有了显示的带参数的构造函数,那么他是无法依靠编译器生成无参构造函数的,所以没有三个int型数据,就无法创建Test的对象。Test类对象是MyTest的成员,想要初始化这个对象test,那就只能用成员初始化列表,没有其他办法将参数传递给Test类构造函数。
注意: 初始化列表在构造函数执行之前执行。
-
情况二的说明:对象引用或者cosnt修饰的数据成员
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
-
情况三的说明:子类初始化父类的私有成员,需要在(并且也只能在)参数初始化列表中显示调用父类的构造函数:
代码如下(参考了之前学习设计模式时候的代码):
#include <iostream>
#include <vector>
using namespace std;
class Context {
public:
bool isMgr;
};
class Base {
public:
Base(Base* c = nullptr){
cc = c;
}
virtual double Calc(Context& ctx) {
printf("base/n");
return 0.0;
}
virtual ~Base() {};
protected:
Base* cc;
};
class Son1 : public Base{
public:
Son1(Base* c) : Base(c) {}
~Son1() {}
virtual double Calc(Context& ctx) {
printf("son1\n");
return 0.0 + cc->Calc(ctx);
}
};
class Son2 : public Base {
public:
Son2(Base* c) : cc(c) {}
~Son2() {}
virtual double Calc(Context& ctx) {
printf("son2\n");
return 0.0 + cc->Calc(ctx);
}
protected:
Base* cc;
};
int main() {
Context ctx1;
ctx1.isMgr = true;
Base* base = new Base(nullptr);
Base* cb1 = new Son1(base);
Base* cb2 = new Son2(cb1);
cb2->Calc(ctx1);
return 0;
}
注意:
-
初始化列表在进行初始化时的顺序和类内成员声明顺序相同,和初始化顺序写法没有关系;
-
默认构造函数
foo(){}
和foo(int i = 0){}
都是默认构造函数。两者不能同时出现 -
在继承中,只有初始化列表可以构造父类的
private
成员(需要显式地调用父类地构造函数)。class foo { private: int a; public: foo(int x){ a = x; } }; class child : public foo{ child(int x):foo(x){}; };