一、什么是初始化列表
与一般函数不同,构造函数除了有名字、形参列表和函数体外,还可以有初始化列表。初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。下面用一个例子来说明初始化列表的使用:
(Point类表示平面上的一个点,有两个私有成员x, y,分别表示该点的x, y坐标的值。)
class Point
{
public:
Point(int xx, int yy) : x(xx), y(yy)
{
cout << "Constructor of Point" << endl;
}
private:
float x, y;
};
上面一段代码在功能上等价于下面的一段代码:
class Point
{
public:
Point(int xx, int yy)
{
x = xx;
y = yy;
cout << "Constructor of Point" << endl;
}
private:
float x, y;
};
上面两段代码对应于初始化类成员的两种方式:(1)使用初始化列表;(2)在构造函数体内进行赋值操作。
但严格来说,上面两段代码只是能实现相同的功能(初始化Point类的对象),它们的本质并不相同,下面来说明原因。
构造函数的执行分为两个阶段:
(1)执行初始化列表:初始化类中的数据成员;
(2)执行构造函数体:一般是对类中的数据成员进行赋值操作。
初始化与赋值是不同的,所以上面两段代码只是功能上相同,但本质并不相同,前一个是初始化,后一个是赋值。
二、使用初始化列表的原因
使用初始化列表的原因主要有两点:(1)在某些情况下我们不得不使用初始化列表;(2)提高效率。
1.必须使用初始化列表的情况
(1)类中有常量类型的数据成员
因为常量只能初始化不能赋值,所以必须在构造函数的初始化列表中对常量类型的数据成员进行初始化。
代码:
class Point
{
public:
Point(float xx, float yy) : x(xx), y(yy)
{
cout << "Constructor of Point" << endl;
}
// error :
// Point(float xx, float yy)
// {
// x = xx;
// y = yy;
// cout << "Constructor of Point" << endl;
// }
private:
const float x, y;
};
(2)类中有引用类型的数据成员
因为引用型变量必须在初始化时绑定引用的对象,所以必须在构造函数的初始化列表中对引用类型的数据成员进行初始化。
代码:
class Point
{
public:
Point(float xx, float yy) : x(xx), y(yy)
{
cout << "Constructor of Point" << endl;
}
// error :
// Point(float xx, float yy)
// {
// x = xx;
// y = yy;
// cout << "Constructor of Point" << endl;
// }
private:
float &x, &y;
};
(3)类中有无默认构造函数的内嵌对象
(内嵌对象的)构造函数只能在(组合类的)构造函数的初始化列表中调用,不能在(组合类的)构造函数体中调用。(reference)
<1>当内嵌对象有默认构造函数时,此时在组合类构造函数的初始化列表中可以不显式地为内嵌对象初始化。因为初始化列表会自动调用内嵌对象的默认构造函数,从而初始化内嵌对象。
代码:(Line类为平面上的一条直线,它由平面上的两个点确定)
#include <iostream>
using namespace std;
class Point
{
public:
Point()
{
cout << "Default constructor of Point" << endl;
}
private:
float x, y;
};
class Line
{
public:
Line()
{
cout << "Default constructor of Line" << endl;
}
private:
Point p1, p2;
};
int main(int argc, char **argv)
{
Line l1;
return 0;
}
输出结果:
Default constructor of Point
Default constructor of Point
Default constructor of Line
由输出结果可以看出,Line类的默认构造函数自动调用Point类的默认构造函数,来初始化Line类的内嵌对象p1和p2。如果没有定义Line类和Point类的默认构造函数,编译器也会自动生成隐含的默认构造函数。
<2>当内嵌对象无默认构造函数时,此时在组合类构造函数的初始化列表中必须显式地为内嵌对象初始化,同时提供必要的参数。因为C++不能自动调用有参数的构造函数,而调用内嵌对象的构造函数时又必须提供参数(因为内嵌对象无默认构造函数)。
代码:
#include <iostream>
using namespace std;
class Point
{
public:
Point(float xx, float yy) : x(xx), y(yy)
{
cout << "Constructor of Point" << endl;
}
Point(const Point& p) : x(p.x), y(p.y)
{
cout << "Copy constructor of Point" << endl;
}
private:
float x, y;
};
class Line
{
public:
Line(Point& pp1, Point& pp2) : p1(pp1), p2(pp2)
{
cout << "Constructor of Line" << endl;
}
private:
Point p1, p2;
};
int main(int argc, char **argv)
{
Point p1(0, 1), p2(1, 0);
Line l1(p1, p2);
return 0;
}
输出结果:
Constructor of Point
Constructor of Point
Copy constructor of Point
Copy constructor of Point
Constructor of Line
由输出结果可以看出,Line类的构造函数会调用Point类的复制构造函数,来初始化Line类的内嵌对象p1和p2。
由以上测试可以得出结论:
组合类中的内嵌对象一定会在组合类构造函数的初始化列表中被初始化。如果内嵌对象无默认构造函数,那么一定要在组合类构造函数的初始化列表中显式地初始化;如果内嵌对象有默认构造函数,那么在组合类构造函数的初始化列表中可以不显式地初始化,但组合类构造函数的初始化列表会自动调用内嵌对象的默认构造函数,从而为其初始化。
(4)基类无默认构造函数
<1>基类有默认构造函数
代码:
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "Default constructor of Base" << endl;
}
private:
float x, y;
};
class Derived : public Base
{
public:
Derived()
{
cout << "Default constructor of Derived" << endl;
}
};
int main(int argc, char **argv)
{
Derived d;
return 0;
}
输出结果:
Default constructor of Base
Default constructor of Derived
<2>基类无默认构造函数
代码:
#include <iostream>
using namespace std;
class Base
{
public:
Base(float xx, float yy) : x(xx), y(yy)
{
cout << "Constructor of Base" << endl;
}
private:
float x, y;
};
class Derived : public Base
{
public:
Derived(float xx, float yy) : Base(xx, yy)
{
cout << "Constructor of Derived" << endl;
}
};
int main(int argc, char **argv)
{
Derived d(0, 0);
return 0;
}
输出结果:
Constructor of Base
Constructor of Derived
以上结果的分析与(3)类似,在此不再赘述。
2.初始化列表是如何提高效率的
(1)先来看不使用初始化列表的情况:
代码:(Family类中内嵌有一个Member类对象成员,其中Member类中有默认构造函数、复制构造函数和重载的赋值运算符以及私有成员age)
#include <iostream>
using namespace std;
class Member
{
public:
Member()
{
cout << "Default constructor of Member" << endl;
}
Member(const Member& m) : age(m.age)
{
cout << "Copy construcor of Member" << endl;
}
Member& operator = (const Member& m)
{
age = m.age;
cout << "Assignment of Member" << endl;
}
private:
int age;
};
class Family
{
public:
Family(Member& m)
{
m1 = m;
cout << "Constructor of Family" << endl;
}
private:
Member m1;
};
int main(int argc, char **argv)
{
Member m1;
Family f1(m1);
return 0;
}
输出结果:
Default constructor of Member
Default constructor of Member
Assignment of Member
Constructor of Family
解释:
输出结果中的第一行对应main中的第一行,构造一个Member类型的对象m1;
输出结果中的第二行对应Family类构造函数中的代码,调用Member类的默认构造函数初始化其私有成员m1;
输出结果中的第三行对应Family类构造函数中的代码,调用重载后的赋值运算符"=",对m1执行赋值操作;
(2)再来看使用初始化列表的情况:
代码:
#include <iostream>
using namespace std;
class Member
{
public:
Member()
{
cout << "Default constructor of Member" << endl;
}
Member(const Member& m) : age(m.age)
{
cout << "Copy constructor of Member" << endl;
}
Member& operator = (const Member& m)
{
age = m.age;
cout << "Assignment of Member" << endl;
}
private:
int age;
};
class Family
{
public:
Family(Member& m) : m1(m)
{
cout << "Constructor of Family" << endl;
}
private:
Member m1;
};
int main(int argc, char **argv)
{
Member m0;
Family f0(m0);
return 0;
}
输出结果:
Default constructor of Member
Copy constructor of Member
Constructor of Family
解释:
输出结果中的第一行对应main中的第一行,构造一个Member类型的对象m0;
输出结果中的第二行对应Family类构造函数中的代码,直接调用Member类的复制构造函数初始化其私有成员m1;
对比(1)(2)我们可以发现,与不使用初始化列表相比,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,能极大地提高效率。所以,在构造函数中应尽量使用初始化列表来完成数据成员的初始化!