【C++】初始化列表
目录
1. 概念
代码示例
2. 构造函数的执行阶段
代码示例
3. 必须使用初始化列表初始化的情况
代码示例1
代码示例2
观察运行下面代码
4. 默认成员初始化器
代码示例 1
代码示例2
代码示例3
代码示例4
观察运行下面代码
5.总结
1. 概念
构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
代码示例
class Date { public:Date(int year, int month, int day):_year(year), _month(month), _day(day) {/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private: int _year = 1;int _month = 1;int _day = 1;};int main() {Date d1(2025, 4, 8);d1.Print();return 0; }
2. 构造函数的执行阶段
- 初始化阶段:在构造函数体 {} 执行之前,会先执行初始化列表中的成员初始化操作。
- 赋值阶段:进入构造函数体后,只能对成员变量进行赋值操作(而非初始化)。
代码示例
class Cat { public:Cat(int val) {a = val; // 构造函数体中赋值(等价于:先默认初始化 a=0,再赋值为 val)} private:int a; 在定义时可以不初始化,因为a是普通成员) };
class Cat { public:Cat(int val):a(val) //直接初始化{/*构造函数体*/} private:int a; };
在构造函数体内对成员进行初始化(本质是赋值):
对于类类型成员,先在初始化阶段调用成员的默认构造函数(生成临时对象),再在构造函数体内调用赋值运算符(operator=)进行赋值,两次操作,效率更低。对于内置类型成员,需要默认初始化+赋值。所以使用初始化列表更高效。
3. 必须使用初始化列表初始化的情况
对于const成员变量、引用成员变量、没有默认构造的类类型变量必须使用初始化列表进行初始化。
代码示例1
const成员变量如果不在初始化列表初始化,编译会报错,因为无法在函数体中赋值,由于语言规则限制,const不允许先默认初始化再赋值,引用同理。
错误写法:
class Dog { public:Dog(int val, int& obj){ //未使用初始化列表x = val; // 错误:对 const 变量赋值ref = obj; // 错误:对未初始化的引用赋值} private:const int x;int& ref; };int main() {int num = 10;Dog d(20, num);return 0; }
正确写法:
class Dog { public:Dog(int val, int& obj):x(val),ref(obj){/*构造函数体*/} private:const int x;int& ref; };int main() {int num = 10;Dog d(20, num);return 0; }
代码示例2
类类型成员变量在初始化阶段必须调用其构造函数,若有默认构造函数,编译器会自动调用其默认构造函数进行初始化(即使不写初始化列表),没有默认构造函数时,必须在初始化列表中显式提供合适的构造函数调用,否则编译器无法自动完成初始化,导致错误。
错误写法:
class Time { public://没有默认构造函数,有带参数的构造函数Time(int hour):_hour(hour){cout << "Time()" << endl;} private:int _hour; };class Date { public://构造函数Date(int year, int month, int day):_year(year), _month(month), _day(day) {/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private: int _year;int _month;int _day;Time _t; };int main() {Date d1(2025, 4, 8);//尝试不使用初始化列表初始化_t成员,会导致编译错误 error:"Time"没有合适的默认构造函数可用!d1.Print();return 0; }
正确写法:
1.
class Time { public://没有默认构造函数,有带参数的构造函数Time(int hour):_hour(hour){cout << "Time()" << endl;} private:int _hour; };class Date { public://构造函数Date(int year, int month, int day):_year(year), _month(month), _day(day),_t(0) //在初始化列表中显式调用Time的带参构造函数{/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;Time _t; };int main() {Date d1(2025, 4, 8);d1.Print();return 0; }
2.
class Time { public://有默认构造函数Time(int hour = 1):_hour(hour){cout << "Time()" << endl;} private:int _hour; };class Date { public://构造函数Date(int year, int month, int day):_year(year), _month(month), _day(day)//即使不写初始化列表_t(0),编译器也会自动调用默认构造函数初始化_t{/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;Time _t; };int main() {Date d1(2025, 4, 8);d1.Print();return 0; }
观察运行下面代码
class Date { public://构造函数Date(int year, int month, int day):_year(year), _month(month)//, _day(day) 这里不初始化_day {/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day; };int main() {Date d1(2025, 4, 8);d1.Print();return 0; }
如果不初始化_day,当调用Print函数输出_day时,输出值是不确定的。
运行结果:
4. 默认成员初始化器
C++新特性——默认成员初始化器(即在成员变量声明处直接赋值),C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
代码示例 1
当类的构造函数没有在成员初始化列表中对该成员变量进行显式初始化时,就会使用默认值来初始化该成员变量,它为成员变量提供了一个默认的初始值,以防构造函数没有对其初始化。
class Date { public://构造函数Date(int year, int month, int day):_year(year), _month(month)//, _day(day) 这里不初始化_day {/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private://C++11引入的新特性——成员变量的默认成员初始化器int _year = 1;int _month = 1;int _day = 1; };int main() {Date d1(2025, 4, 8);d1.Print();return 0; }
运行结果:
如果构造函数的初始化列表对成员变量进行了初始化(例如上面_year(year), _month(month))会优先使用初始化列表中的值,而不会使用默认成员初始化器的值。
代码示例2
当类中没有显示定义任何构造函数时,编译器会自动生成一个默认构造函数。对于:
- 具有默认成员初始化器的成员变量:默认构造函数会使用这些默认值进行初始化。
- 没有默认成员初始化器的成员变量:对于内置类型,值未定义;对于用户自定义类型,会调用其默认构造函数,没有则报错。
class Time { public://默认构造函数Time(int hour = 0):_hour(hour){cout << _hour << endl;} private:int _hour; };class Date { public://无构造函数void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private://默认成员初始化器int _year; //无 内置类型,其值是未定义的int _month = 2; //有int _day = 2; //有Time _t; //无 自定义类型,调用它的默认构造函数,如果没有,编译报错 };int main() {Date d1;d1.Print();return 0; }
运行结果:
代码示例3
类类型成员未在初始化列表中显示初始化:
- 当类类型成员没有默认成员初始化器时:则必须调用默认构造函数,没有则报错。
- 当类类型成员有默认成员初始化器时:则根据默认成员初始化器的值调用对应的构造函数(不要求默认构造函数存在)。
内置类型成员未在初始化列表中显示初始化:
- 若声明时有默认成员初始化器:使用该默认值初始化。
- 若声明时无默认成员初始化器:未初始化,编译器不会自动初始化,值是垃圾值。
class Time { public: Time(int hour):_hour(hour){cout << _hour << endl;} private:int _hour; }; class Date { public://构造函数Date(int year, int month, int day):_year(year) , _month(month){/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private://默认成员初始化器int _year; //无int _month = 2; //有int _day = 2; //有 在初始化列表中未被初始化,使用默认成员初始化器将其初始化为2Time _t = 3; //有,就按照默认成员初始化器的值,调用构造函数(将3作为参数传递进去来初始化_t) };int main() {Date d1(2025, 4, 8);d1.Print();return 0; }
运行结果:
过程分析:
首先编译器会检查Date类的构造函数的成员初始化列表,发现初始化列表里没有对_t的初始化,接着查看_t是否有默认成员变量初始化器,最后考虑构造函数的调用,由于 Time _t = 3; 调用Time类的构造函数初始化_t。
代码示例4
初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
下面程序的运行结果是什么?#include<iostream> using namespace std;class A { public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;} private:int _a2 = 2;int _a1 = 2; };int main() {A aa(1);aa.Print(); }
A. 输出 1 1 B. 输出 2 2 C. 编译报错D. 输出1 随机值 E. 输出 1 2 F. 输出 2 1上面程序的运行结果是: D
观察运行下面代码
class Date
{
public:Date(int year = 1, int month = 1, int day = 1)//每个参数都提供了默认值:_year(year) //使用默认值初始化_year和_month 而_day没有在初始化列表中初始化也没有默认成员初始化器,它的值是不确定的, _month(month){/*构造函数体*/}void Print()const{cout << _year << "/" << _month << "/" << _day << endl;}private://默认成员初始化器int _year = 3; //有int _month = 3; //有int _day; //无
};int main()
{ Date d1;d1.Print();return 0;
}
运行结果:
5.总结
尽量使用初始化列表初始化,不在初始化列表初始化的成员也会走初始化列表因为所有成员包括内置类型和类类型的初始化必定发生在构造函数体执行之前。(没有显示出现在初始化列表中的成员,对于内置类型,执行默认初始化,对于类类型的成员,编译器也会隐式地在初始化阶段调用它们的默认构造函数,因此这些成员的初始化过程仍然发生在构造函数体之前,即“走”了初始化列表的过程)
若成员声明时有默认成员初始化器,且初始化列表未显示初始化该成员,则使用该缺省值进行初始化;若内置类型成员未在初始化列表中显式初始化,且声明时没有默认成员初始化器,其值是未定义的。若类类型成员未在初始化列表中显式初始化,且声明时没有默认成员初始化器,则必须调用默认构造函数,没有则报错。
每个构造函数都有初始化列表,如果没有显式地写出初始化列表,编译器会隐式地为构造函数生成一个初始化列表。在这个隐式的初始化列表中,类的成员变量会按照它们在类中声明的顺序进行初始化。对于类类型的成员变量,会调用其默认构造函数进行初始化;对于内置类型的成员变量,会进行默认初始化。