一、初始化列表定义
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Date { public: Date(int year, int month, int day) : _year(year) , _month(month)//初始化列表 , _day(day) {} private: int _year; int _month; int _day; };
二、初始化列表内在作用的剖析
不知道大家有没有想过这样一个问题,成员函数明明可以在函数内部对成员变量进行赋值,那为什么还要搞出初始化列表这个东西呢?这个时候就需要我们对初始化列表有一个更加深刻的理解了。
我们知道,在一个类被设计出来的时候,它里面定义的成员变量只是变量的声明,没有为其分配空间。我们也知道,类定义出一个对象是在主函数中定义的,而创建出来的这个对象其中的成员变量其实是在初始化列表中定义的。那么假设我们创建出来的类的成员变量中含有引用类型或者是被const修饰时(引用类型和const修饰的变量在定义时就必须为其赋初始值),就像下面这个代码:
如果我们定义的类内部的成员变量中含有引用类型或者是被const修饰,而在成员函数体内部再对其赋初始值,就相当于_ref和_n两个变量定义和赋初始值分离了,但我们明确地知道引用类型或者是被const修饰的变量在定义时就必须为其赋初始值,所以编译器会报未初始化的错误。上面图片中的代码还可以写的更明确一点,就相当于下面这种形式:
五个成员变量全部定义和赋初始值分开,一般的内置类型是支持这种行为的。就像是这样:
int main() { int a; a = 10;//可以 int& n; n = a;//报错 const int m; m = 10;//报错 return 0; }
所以正确的方法应该是:类内部的成员变量中含有引用类型或者是被const修饰时,引用类型或者是被const修饰的成员变量必须用初始化列表赋初值(定义时就赋初值)。
class Date { private: int _year; int _month; int _day; int& _ref; const int _n; public: Date(int year, int month, int day) :_year() ,_month() ,_day() ,_ref(month) ,_n(1) { this->_year = year; this->_month = month; this->_day = day; } };
三、类中成员变量含有自定义类型的情况
很好理解,自定义也必须使用初始化列表进行初始化,如果自定义类型没有显示地调用初始化列表,那么自定义类型就会去调用它的默认构造函数,如果没有默认构造函数,就会编译报错。
#include <iostream> using namespace std; class A { private: int _a; public: A(int a = 0) { this->_a = a; } }; class Date { private: int _year; int _month; int _day; A aa; int& _ref; const int _n; public: Date(int year, int month, int day) :_year() //aa没有显示地调用初始化列表,会去调用它的默认构造函数 , _month()//剩下的三个成员没有写出来定义,但是它也会定义,只是内置类型给的随机值 , _day() //自定义类型会去调用它的默认构造函数 ,_ref(month) ,_n(1) { this->_year = year; this->_month = month; this->_day = day; } }; //编译没有报错 int main() { Date d1(2023, 11, 2); return 0; }
_a被初始化为了0。还是上面这段代码,如果将A(int a = 0)改成A(int a),编译就会报错,因为没有合适的默认构造函数。
所以自定义类型在使用初始化列表的时候,建议要显示地传参去调用指定的构造函数。
四、初始化列表使用的建议以及小点
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。
但是我们能不能什么地方都用初始化而都不在函数体内部赋初始值呢?答案是不行的。初始化列表可以解决百分之九十的问题,但对于一些像判断之类的语句还是要放在函数体内部的。像下面这个例子:
class Date { private: int _year; int _month; int _day; int* _aa; public: Date(int year, int month, int day) :_year(year) //aa没有显示地调用初始化列表,会去调用它的默认构造函数 , _month(month)//剩下的三个成员没有写出来定义,但是它也会定义,只是内置类型给的随机值 , _day(day) //自定义类型会去调用它的默认构造函数 ,_aa(new int [10]) { if (_aa == nullptr) { perror("new fail"); exit(-1); } } ~Date() { delete[] _aa; } }; int main() { Date d1(2023, 11, 2); return 0; }
这里有一个小点需要注意:. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2; int _a1; }; int main() { A aa(1); aa.Print(); }
上面代码_a2比_a1先声明,所以_a2先初始化,用_a1初始化_a2,此时_a1为随机值,所以初始化完_a2为随机值,再用1初始化_a1,_a1为1。