1.初始化列表:
与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
struct foo {
string name ;
int id ;
foo(string s, int i):name(s), id(i){} ; // 初始化列表
};
2.
构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。
3.何时使用初始化列表:
c++语言规定,不能在类体中直接指定数据成员的初值,所以对象的初始化工作职能通过调用对象的构造函数来完成。初始化列表在构造函数中扮演了重要角色。
对于普通的数据成员而言,使用初始化列表和在构造函数体内赋值,效果是一样的。在另外一些情况下,只能使用初始化列表对成员进行初始化,否则会发生编译错误。例如,数据成员是引用、常量、类对象(该类没有提供不带参数的构造函数)等。如下:
/*******Program 1*********/
#include <iostream>
using namespace std;
class A{
int i;
public:
A(){
i = 1;
}
void show(){
cout<<i<<endl;
}
};
class B{
int i;
public:
B():i(1){}
void show(){
cout<<i<<endl;
}
};
int main(){
A().show();
B().show();
}
/*******end of Program 1*********/
输出结果:
1
1
类A的构造函数和类B的构造函数实际上没有任何差别。
/******* Program 2*********/
#include <iostream>
using namespace std;
class A{
int num;
public:
A(int i){
num = i;
}
void show(){
cout<<num<<endl;
}
};
class B{
int &r;
const double PI;
A a;
public:
B(int i){}
void show(){
cout<<r<<endl;
cout<<PI<<endl;
a.show();
}
};
int e = 5;
B::B(int i):r(e),PI(3.1415926),a(i){}
int main(){
B(1).show();
}
/*******end of Program 2*********/
输出结果:
5
3.1415926
1
如果在类B的构造函数中,将初始化列表中的任何一个成员的初始化工作移到函数体内,都会导致变异错误::
B::B(int i){
r = e; // 这是为引用赋值,并不是初始化
PI = 3.1415926; // 常量的值不允许改变
a = A(i); // 在类B的构造函数调用之前,会先调用类A的构造函数A(),而此时函数无定义
}
4.初始化列表提高程序运行效率:
有时利用对象之间的赋值来代替初始化,而不是显示使用初始化列表,这样虽然不会发生编译错误,但会造成程序运行效率降低:
/*******Program 3*********/
#include <iostream>
using namespace std;
class A{
int num;
public:
A(){
cout<<"In default constructor"<<end;
}
A(int i){
cout<<"In user-defined constructor"<<end;
num = i;
}
A & operator=(const A& a){
cout<<"Using assignment"<<endl;
num = a.num;
return *this;
}
void show(){
}
};
class B{
A a;
public:
B(int i){}
void show(){
a.show();
}
};
B::B(int i){
a = A(i);
}
int main(){
B b(1);
b.show();
}
/*******end of Program 3*********/
输出结果:
In default constructor
In user-defined constructor
Using assignment
1
没有显示地在初始化列表中对成员对象a进行初始化,则在进入类B的构造函数体之前,会调用类A的默认构造函数。在类B的构造函数体内进行的是赋值操作,并且要调用类A的构造函数A(int)先生成一个无名对象。这种为成员对象初始化的方式不仅逻辑结构不清晰,而且效率低下。如果将B的构造函数改写成:
B::B(int i):a(i){}
则程序的输出结果变成::
In user-defined constructor
1
这样效率明显提高
表面上看,有很多类的构造函数根本没有使用初始化列表。但实际上,有些内容不管有没有显示写进初始化列表,都是会被“强行”加进去的:
(1)如果该类是某个类的派生类,那么他的直接基类的构造函数一定要出现在初始化列表中。要么是程序员显示地在初始化列表中调用直接基类的构造函数,否则编译器把直接基类的默认构造函数插入到初始化列表中。
(2)如果该类包含其他类的对象作为其数据成员,那么这些对象的初始化工作也一定要在初始化列表中完成。要么是程序员显示地在初始化列表中给出对象的构造形式,否则编译器会在初始化列表中调用这些对象的默认构造函数来完成初始化。
5.成员变量的初始化顺序:
基类的构造函数最先执行,其他的成员按照它们在类中声明的顺序依次被初始化,而不是按照它们在初始化列表出现的顺序初始化。
/*******Program 4*********/
#include <iostream>
using namespace std;
class A{
int num;
public:
A(int i){
cout<<"In A constructor"<<end;
num = i;
}
};
class B{
public:
int num;
B(int i){
num = i;
cout<<"In B constructor"<<endl;
}
};
class C{
protected:
int num;
public:
C(int i){
num = i;
cout<<"In C constructor"<<endl;
}
};
class D:public C{
A a;
int num;
B b;
public:
D(int i):num(i++),b(i++),a(i++),C(i++){}
void show(){
cout<<"C::num="<<C::num<<endl;
cout<<"a.num="<<a.num<<endl;
cout<<"num="<<num<<endl;
cout<<"b.num="<<b.num<<endl;
}
};
int main(){
D d(1);
d.show();
}
/*******end of Program 4*********/
输出结果:
In C construtor
In A construtor
In B construtor
C::num=1
a.num=2
num=3
b.num=4
6. 在初始化列表中,无法完成为对象的数组成员进行初始化的工作,原因是C++语言没有提供这样的机制,所以只能在构造函数体内分别为成员数组的每个元素分别赋初值。如果数组元素本身也是对象,那么这种赋值操作会造成较大的运行时开销。问题还不止这些。如果类体中定义一个常量数组,如何进行初始化呢?
在实际应用中,通常只会在类中定义静态的常量数组,这样就可以完成数组的初始化工作。如果确实想在类中定义一个常量数组,一个变通的做法是定义一个指向常量的指针常量,然后在初始化列表中为它初始化。
/******Program 5*********/
#include <iostream>
using namespace std;
int *CreateArr(int n){
int *p;
p = new int[n];
for (int i=0;i<n;i++)
{
p[i] = i+1;
}
return p;
}
class A{
int arrsize;
const int* const arr;
public:
A(int n):arr(CreateArr(n)){
arrsize=n;
}
void show(){
for (int i=0;i<arrsize;i++)
{
cout<<arr[i]<<' ';
}
cout<<endl;
}
~A(){
delete[] arr;
}
};
int main(){
A a(3);
a.show();
}
/*******end of Program 5*********/
输出结果:
1 2 3
由于实际上在构造函数中为arr分配了空间,所以应该在类A的析构函数中释放这部分空间
~A(){
delete[] arr;
}
delete[] arr;
}
这样保证不发生内存泄露。