初始化列表
引入
class Date
{
public:
Date(int _year, int _month, int _day)
{
this->_year = _year;
this->_month = _month;
this->_day = _day;
}
private:
int _year;
int _month;
int _day;
};
进行对象的实例化时,编译器会自动调用构造函数,给成员变量一个初始值。
我们应该清楚地是:在构造函数体内进行的是赋值操作,而不是初始化操作。
我们可以进行验证:
我们添加一个必须进行初始化操作的成员变量a
,并且在构造函数内给予它一个值。我们发现编译器报错了:编译器认为a
没有进行初始化操作。
这也验证了我们的说法。
那么,对于我们的必须进行初始化操作的成员变量,我们怎么办呢?我们引入初始化列表来解决这个问题。
class Date
{
public:
Date(int _year, int _month, int _day, int& a)
:_year(_year)
, _month(_month)
, _day(_day)
, a(a)
{ }
private:
int _year;
int _month;
int _day;
int& a;
};
我们发现编译器并没有报错。说明在初始化列表中进行的是初始化操作。
不过在使用初始化列表时,我们有几个注意事项。
注意
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(该类没有默认构造函数)class A { public: A(int a) :_a(a) {} private: int _a; }; class B { public: B(int a, int ref) :_a(a) , _ref(ref) , _n(10) {} private: A _a; // 没有默认构造函数 int& _ref; // 引用 const int _n; // const };
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A constructor" << endl;
}
private:
int _a;
};
class B
{
public:
B()
{}
private:
A _a;
};
int main()
{
B b;
return 0;
}
虽然我们没有写初始化列表,但是初始化列表的确是成员变量定义的地方,依然存在着。如上述程序。自定义成员变量_a依然调用了构造函数。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
例题:
结果为:1 随机值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(); }
**建议:**一个类,尽量声明的顺序和初始化列表出现的顺序保持一致,这样不易出现问题。
总结
初始化列表就是给成员变量找一个依次定义处理的地方。
- 初始化列表 - 成员变量定义的地方。
- const、引用、没有默认构造函数的自定义类型成员变量必须在初始化列表初始化,因为他们都必须在定义的时候初始化。
- 对于其他类型变量,在哪初始化都可以。
- 建议:对于内置类型的成员变量,在函数体内和在初始化列表处进行初始化操作都可以;对于自定义类型的成员变量,建议在初始化列表进行初始化,这样做更高效。
- 对于存在堆中成员变量时,小心深浅拷贝问题。
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
class Date
{
public:
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018);
// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2022构造一个无名对象,最后用无名对象给d1对象进行赋值
d1 = 2022;
return 0;
}
用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。
小细节
class Date
{
public:
Date(int year)
:_year(year)
{
cout << "Date constructor" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018);
d1 = 2022;
return 0;
}
本来用2022构造一个临时对象Date(2022),再用这个对象拷贝构造d2。
但是C++编译器在连续的一个过程中,多个构造会被优化,合二为一。所以这里被优化为直接就是一个构造。
小补充
// 隐式类型转换 - 相近类型 -- 表示意义相似的类型
double d = 1.1;
int i = d;
const int& i = d;
// 强制类型转换 - 无关类型
int* p = &i;
int j = (int)p;