C++ 构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。例如:
class A { public: int a; float b; A(): a(0),b(9.9) {} //构造函数初始化列表 }; class A { public: int a; float b; A() //构造函数内部赋值 { a = 0; b = 9.9; } };
上面的例子中两个构造函数的效果是一样的。使用初始化列表的构造函数是显示地初始化类的成员;而没有使用初始化列表的构造函数是对类的成员赋值,并没有显示地初始化。
初始化列表的构造函数和内部赋值的构造函数对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。
用构造函数的初始化列表来进行初始化,写法方便、简练,尤其当需要初始化的数据成员较多时更显其优越性。对非内置类型成员变量,推荐使用类构造函数初始化列表。
但有的时候必须用带有初始化列表的构造函数:(1)没有默认构造函数的成员类对象;(2)const成员或引用类型的成员。
构造函数中有着比我们所看见的还要多的细节,构造函数可以调用其它的构造函数来初始化对象中的基类对象和成员对象的构造函数。
类的数据成员中的其它类对象,若该成员类型是没有默认构造函数,则必须进行显示初始化,因为编译器会隐式调用成员类型的默认构造函数,而它又没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
例:
class A { public: A (int x) { i = x; // 无默认构造函数 } private: int i; }; class B { public: B(int y) { j = y; // error C2512: “A”: 没有合适的默认构造函数可用 } private: A a; int j; }; int main( ) { B b(5); return 0; }
B类数据成员中有一个A类对象a,创建B类对象时,要先创建其成员对象a;A类有一个参数化大的构造函数,则编译器不会提供默认无参数的构造函数,因此a无法创建。
对成员对象正确的初始化方法是通过显示方式进行,B的构造函数应该写成:
B(int y, int z) : a(z) { j = y; } B b(5,10);
构造函数初始化列表是初始化常数据成员和引用成员的唯一方式。因为const对象或引用类型只能初始化,不能对他们赋值。
class A { public: A (int x,int y) : c(x),j(y) // 构造函数初始化列表 { i = -1; } private: int i; const int c; int& j; }; int main( ) { int m; A a(5,m); return 0; }
若不通过初始化列表来对常数据成员和引用成员进行初始化:
class A { public: A (int x) // 构造函数初始化列表 { i = -1; c = 5; j = x; } private: int i; const int c; // error C2758: “A::c”: 必须在构造函数基/成员初始值设定项列表中初始化 int& j; // error C2758: “A::j”: 必须在构造函数基/成员初始值设定项列表中初始化 };
缺省情况下,在构造函数的被执行前,对象中的所有成员都已经被它们的缺省构造函数初始化了。当类中某个数据成员本身也是一个类对象时,我们应该避免使用赋值操作来对该成员进行初始化:
class Person { private: string name; public: Person(string& n) { name = n; } }
虽然这样的构造函数也能达到正确的结果,但这样写效率并不高。当一个Person对象创建时,string类成员对象name先会被缺省构造函数进行初始化,然后在Person类构造函数中,它的值又会因赋值操作而在改变一次。我们可以通过初始化列表来显示地对name进行初始化,这样便将上边的两步(初始化和赋值)合并到一个步骤中了。
class Person { private: string name; public: Person(string& n): name(n){} }