一.再谈构造函数
构造函数其实分为:
1.函数体赋值
2.初始化列表
之前所讲到的构造函数其实都是函数体赋值,那么本篇文章将会具体讲述初始化列表。
初始化列表
语法
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
1. class Date 2. { 3. public: 4. Date(int year, int month, int day) 5. : _year(year) 6. , _month(month) 7. , _day(day) 8. {} 9. private: 10. int _year; 11. int _month; 12. int _day; 13. };
上述代码即是初始化列表。
必须用初始化列表初始化的变量
需要注意的是,有几种变量必须要用初始化列表初始化:
1.const 变量
2.引用变量
3.自定义变量
接下来我们一个一看。
const 变量
可以看到在函数体中对 const 变量是不可以初始化的,所以必须要在初始化列表中初始化;
引用变量
很明显,对于引用变量也不能在函数体中初始化;这里还要注意给引用传参时,也要传引用,否则会出现类似野引用的情况,这种情况很危险。
自定义变量
对于自自定义变量,会去调用它的默认构造函数,所以不显式初始化自定义变量也行,但如果该自定义变量没有默认构造函数的话,就必须要显式初始化(关于默认构造函数:构造函数和析构函数)
如上图所示,对于没有默认构造函数的自定义变量,因为未显示初始化,所以编译器报了错。
初始化列表的一个坑
我们先来看一段代码:
1. class A 2. { 3. public: 4. A(int a) 5. :_a1(a) 6. ,_a2(_a1) 7. {} 8. 9. void Print() 10. { 11. cout<<_a1<<" "<<_a2<<endl; 12. } 13. private: 14. int _a2; 15. int _a1; 16. }; 17. int main() 18. { 19. A aa(1); 20. aa.Print(); 21. }
上面这段代码会输出什么呢?
答案是:1 随机值
为什么?
这就不得不说到初始化列表的一个有点坑的地方了。
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关;也就是说,上述代码的初始化列表中,先初始化的是 _a2 变量,而 _a2 变量是初始化成 _a1 变量的,但此时 _a1 变量还没有初始化,所以就出现了随机值。
所以呢,初始化列表时最好按照声明的顺序初始化。
总结
1.初始化列表其实是成员变量定义的地方,不管有没有写都会走一遍,且也只会走一遍;
private 中的其实是成员变量的声明;
2.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变 量,一定会先使用初始化列表初始化。
3.初始化列表并不能完全代替函数体赋值。
二.explicit 关键字
内置类型与自定义类型的隐式类型转换
先看这样一段1代码
1. class A 2. { 3. public: 4. A(int a=1) 5. :_a(a) 6. {} 7. private: 8. int _a; 9. }; 10. 11. int main() 12. { 13. A a1(1); 14. A a2 = 2; //这句代码有问题吗 15. 16. return 0; 17. }
我们发现了一个令人有点摸不着头脑的代码: A a2=2 ;
这是什么?
其实这就是隐式类型转换;
内置类型先转换成自定义类型,然后构造一个A的临时对象(临时对象具有常属性),临时对象再拷贝构造a2 ,最后再调用构造函数,但是现在的编译器一般都会对这一过程进行优化,它是直接构造。
我们可以验证下: