对于常量类型和引用类型的数据成员,不能在构造函数中用赋值语句直接赋值,C++提供初始化表进行置初值。
带有成员初始化表的构造函数的一般形式如下:
类名::构造函数名([参数表])[:(成员初始化表)]
{
// 构造函数体
}
成员初始化表的一般形式为:
数据成员名1(初始值1),数据成员名2(初始值2),……
C++构造函数中初始化成员参数列表初始化成员(必须用的原因:对象成员的初始化,const修饰的成员的初始化,引用成员的初始化,子类调用父类的构造函数初始化父类成员)参数列表在构造函数执行之前执行,参数列表中执行的是初始化(所有的成员,无论是否出现在参数列表中,都会有初始化),参数列表的执行顺序与类中成员的声明顺序,与类的继承顺序相一致构造函数中执行的一般是赋值多重继承,虚继承构造函数的参数初始化列表的区别
类对象的构造顺序:
1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员;
2.进入构造函数后在构造函数中执行一般赋值与计算。
使用初始化列表有两个原因:
原因1.必须这样做:
以下三种情况下需要使用初始化成员列表:
一、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
二、需要初始化const修饰的类成员;
三、需要初始化引用成员数据;
例一、数据成员是对象,且对象只有含参数的构造函数;
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。
class Test
{
public:
Test(int x,int y,int z);
private:
int a;
int b;
int c;
};
class MyTest
{
public:
MyTest():test(1,2,3){} //初始化,初始化列表在构造函数执行前执行(这个可以测试,对同一个变量在初始化列表和构造函数中分别初始化,首先执行参数列表,后在函数体内赋值,后者会覆盖前者)。
private:
Test test; //声明
};
因为Test有了显示的带参数的构造函数,那么他是无法依靠编译器生成无参构造函数的,所以没有三个int型数据,就无法创建Test的对象。
Test类对象是MyTest的成员,想要初始化这个对象test,那就只能用成员初始化列表,没有其他办法将参数传递给Test类构造函数。
例二、对象引用或者cosnt修饰的数据成员
另一种情况是这样的:当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
class Test
{
priate:
const int a; //const成员声明
public:
Test():a(10){} //初始化
};
或
class Test
{
private:
int &a; //声明
public:
Test(int a):a(a){} //初始化
}
例三、子类初始化父类的私有成员,需要在(并且也只能在)参数初始化列表中显示调用父类的构造函数,如下:
class Test
{
private:
int a;
int b;
int c;
public:
Test(int a,int b,int c)
{
this->a = a;
this->b = b;
this->c = c;
}
int getA(){return a;}
int getB(){return b;}
int getC(){return c;}
};
class MyTest:public Test
{
private:
int d;
public:
MyTest(int a,int b,int c,int d):Test(a,b,c)
//MyTest(int a,int b,int c,int d)
{
//Test(a,b,c); //构造函数只能在初始化列表中被显示调用,不能在构造函数内部被显示调用
this->d = d;
}
int getD(){return d;}
};
int main(int argc,char *argv[])
{
MyTest mytest(1,2,3,4);
printf("a=%d,b=%d,c=%d,d=%d\n",
mytest.getA(),mytest.getB(),mytest.getC(),mytest.getD());
return 0;
}
多重继承,虚继承构造函数的参数初始化列表的区别:
代码如下:
注意多重继承子类的构造函数
#include
class CTop
{
private:
int a;
public:
int getA()
{
return a;
}
CTop(int a)
{
this->a = a;
}
};
class CLeft:public CTop
{
private:
int b;
public:
int getL()
{
return b;
}
CLeft(int a,int b):CTop(a)
{
this->b = b;
}
};
class CRight:public CTop
{
private:
int c;
public:
int getR()
{
return c;
}
CRight(int a,int c):CTop(a)
{
this->c = c;
}
};
class Test:public CLeft,public CRight
{
private:
int d;
public:
int getT()
{
return d;
}
Test(int a,int b,int c,int d):CLeft(a,b),CRight(a,c)
{
this->d = d;
}
};
int main(int argc,char *argv[])
{
Test obj(1,2,3,4);
printf("obj.a=%d,obj.b=%d,obj.c=%d,obj.d=%d\n",
obj.CLeft::getA(),obj.getL(),obj.getR(),obj.getT()); //getA有歧义,要用类名来做区分。
return 0;
}
注意虚继承子类的构造函数
#include
class CTop
{
private:
int a;
public:
int getA()
{
return a;
}
CTop(int a)
{
this->a = a;
}
};
class CLeft:virtual public CTop
{
private:
int b;
public:
int getL()
{
return b;
}
CLeft(int a,int b):CTop(a)
{
this->b = b;
}
};
class CRight:virtual public CTop
{
private:
int c;
public:
int getR()
{
return c;
}
CRight(int a,int c):CTop(a)
{
this->c = c;
}
};
class Test:public CLeft,public CRight
{
private:
int d;
public:
int getT()
{
return d;
}
Test(int a,int b,int c,int d):CLeft(a,b),CRight(a,c),CTop(a)
{
this->d = d;
}
};
int main(int argc,char *argv[])
{
Test obj(1,2,3,4);
printf("obj.a=%d,obj.b=%d,obj.c=%d,obj.d=%d\n",
obj.getA(),obj.getL(),obj.getR(),obj.getT()); //因为采用虚基类,虚继承机制保证了a只有一份,所以不存在歧义。
return 0;
}
原因2.效率要求这样做:
类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。
注意:构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关,所以要特别注意,保证两者顺序一致才能真正保证其效率和准确性。
为了说明清楚,假设有这样一个类:
class foo{
private :
int a, b;
};
1、foo(){}和foo(int i = 0){}都被认为是默认构造函数,因为后者是默认参数。两者不能同时出现。
2、构造函数列表的初始化方式不是按照列表的的顺序,而是按照变量声明的顺序。比如foo里面,a在b之前,那么会先构造a再构造b。所以无论foo():a(b + 1), b(2){}还是foo():b(2),a(b+1){}都不会让a得到期望的值。
3、构造函数列表能够对const成员初始化。比如foo里面有一个int const c;则foo(int x) : c(x){}可以让c值赋成x。
不过需要注意的是,c必须在每个构造函数(如果有多个)都有值。
4、在继承里面,只有初始化列表可以构造父类的private成员(通过显示调用父类的构造函数)。比如说:
class child : public foo
{
};
foo里面的构造函数是这样写的:
foo (int x)
{
a = x;
}.
而在child里面写child(int x){ foo(x); }是通过不了编译的。
只有把子类构造函数写作child (int x) : foo(x){}才可以。