创建一个类对象时,编译器通过调用构造函数,给类对象中各个成员变量赋初值:
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. { 7. _year = year; 8. _month = month; 9. _day = day; 10. } 11. 12. private: 13. int _year; 14. int _month; 15. int _day; 16. };
但上述赋初值不能称作类对象成员的初始化,因为构造函数体内可以多次赋值:
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. { 7. _year = year; 8. _month = month; 9. _day = day; 10. _year = 2023;//构造函数体内允许对成员变量进行多次赋值 11. } 12. 13. private: 14. int _year; 15. int _month; 16. int _day; 17. };
而初始化列表能只能初始化一次。
一、用初始化列表初始化对象
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括 号中的初始值或表达式。
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. :_year(year) //初始化列表初始化 7. ,_month(month) 8. ,_day(day) 9. {} 10. 11. private: 12. int _year; 13. int _month; 14. int _day; 15. };
(1)初始化列表能只能初始化一次,多次初始化会报错:
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. :_year(year) 7. ,_month(month) 8. ,_day(day) 9. ,_month(month) //初始化列表多次初始化 10. {} 11. 12. private: 13. int _year; 14. int _month; 15. int _day; 16. };
编译器也允许构造函数赋初值和初始化列表初始化混用:
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. :_year(year) //两者混用 7. ,_month(month) 8. { 9. _day = day; 10. } 11. 12. private: 13. int _year; 14. int _month; 15. int _day; 16. };
混用时初始化列表初始化和构造函数赋初值不冲突:
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. : _year(year) //两者不冲突 7. , _month(month) 8. { 9. _day = day; 10. _year = 2023; 11. } 12. 13. private: 14. int _year; 15. int _month; 16. int _day; 17. };
但混用时初始化列表初始化还是要遵循只能初始化一次成员变量的原则:
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. : _year(year) //初始化列表初始化 7. , _month(month) 8. , _year(2023) //_year在初始化列表里被初始化了两次,不允许 9. { 10. _day = day; 11. } 12. 13. private: 14. int _year; 15. int _month; 16. int _day; 17. };
(2)const成员变量、引用成员变量、没有默认构造函数的自定义类型成员只能在初始化列表初始化。
①const成员变量必须在定义的时候初始化
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. : _year(year) 7. , _month(month) 8. , _n(2) //const成员变量必须使用初始化列表进行初始化 9. { 10. _day = day; 11. //_n = 2; //const成员变量不能在函数体内初始化 12. } 13. 14. private: 15. int _year; 16. int _month; 17. int _day; 18. 19. const int _n = 1; 20. };
②引用成员变量必须在定义的时候初始化
1. class Date 2. { 3. public: 4. //构造函数 5. Date(int year = 2022, int month = 4, int day = 19) 6. : _year(year) 7. , _month(month) 8. ,_ref(year)//引用成员变量要在初始化列表初始化 9. { 10. _day = day; 11. //_ref = year; //引用成员变量不能在函数体内初始化 12. } 13. 14. private: 15. int _year; 16. int _month; 17. int _day; 18. 19. int& _ref; 20. };
③没有默认构造函数的自定义类型成员变量
1. #include <iostream> 2. using namespace std; 3. 4. class A 5. { 6. public: 7. 8. //默认构造函数是不用传参就可以调用的构造函数,有3种: 9. //1.无参默认构造函数 10. //2.带参全缺省的默认构造函数 11. //3.我们不写,编译器自动生成的默认构造函数 12. 13. A(int x)//不属于以上任何一种,所以A类的对象没有默认构造函数 14. { 15. cout << "A(int x)" << endl; 16. _x = x; 17. } 18. 19. private: 20. int _x; 21. }; 22. 23. class Date 24. { 25. public: 26. //构造函数 27. Date(int year = 2022, int month = 4, int day = 19) 28. : _year(year) 29. , _month(month) 30. , _a(20)//没有默认构造函数的自定义类型成员变量必须在初始化列表进行初始化 31. { 32. _day = day; 33. } 34. 35. private: 36. int _year; 37. int _month; 38. int _day; 39. 40. A _a; 41. };
const成员变量、引用成员变量、没有默认构造函数的自定义类型成员变量必须在初始化列表内初始化的原因:
①初始化列表是对象的成员变量定义的地方。
②对象的内置类型成员变量在初始化列表定义时没有要求必须初始化,因此既可以在初始化列表进行初始化,也可以在构造函数体内初始化。
③而const成员变量、引用成员变量、没有默认构造函数的自定义类型成员变量不能先定义再初始化,它们在初始化列表内定义,并且必须在定义时就初始化,因此必须在初始化列表内初始化。
(3) 尽量使用初始化列表初始化,因为不管是否使用初始化列表,虽然对于内置类型没有差别,但是对于自定义类型成员变量,一定会先使用初始化列表初始化。
为什么会先使用初始化列表初始化?
如下,Date类没有默认构造函数,因为26行的构造函数不属于默认构造函数中的任意一种,在对Date类的对象d进行初始化时,会调用Date类的默认构造函数,所以对象d的day实参12和hour实参12都没有被传进去,_t作为Date类的自定义类型成员变量会调用Time类的默认构造函数,_hour默认传参为0,因此打印_hour的值也为0,d的参数没有传成功:
1. #include<iostream> 2. using namespace std; 3. 4. class Date; // 前置声明 5. class Time 6. { 7. friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量 8. public: 9. Time(int hour = 0) 10. : _hour(hour) 11. { 12. cout << _hour << endl; 13. } 14. 15. private: 16. int _hour; 17. }; 18. 19. class Date 20. { 21. public: 22. Date(int day, int hour) 23. {} 24. 25. private: 26. int _day; 27. Time _t; 28. }; 29. 30. int main() 31. { 32. Date d(12, 12); 33. return 0; 34. }
假如Date类的构造函数不使用初始化列表进行初始化,使用函数体内初始化时,要把Date类的构造函数的形参hour的值给d,那么就必须构造一个Time类对象t,对该对象传参传hour,再使用赋值运算符重载函数将对象t拷贝给_t:
1. #include<iostream> 2. using namespace std; 3. 4. class Date; // 前置声明 5. class Time 6. { 7. friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量 8. public: 9. Time(int hour = 0) 10. : _hour(hour) 11. { 12. cout << _hour << endl; 13. } 14. 15. private: 16. int _hour; 17. }; 18. 19. class Date 20. { 21. public: 22. //自定义类型,不使用初始化列表,就需要使用构造函数 + operator= 23. Date(int day, int hour) 24. { 25. //函数体内初始化 26. Time t(hour);//调用Time类的构造函数 27. _t = t; 28. 29. _day = day; 30. } 31. 32. private: 33. int _day; 34. Time _t; 35. }; 36. 37. int main() 38. { 39. Date d(12, 12); 40. cout << 4 << endl; 41. return 0; 42. }
这还不如直接使用使用初始化列表初始化呢,还不需要赋值运算符重载函数:
1. class Date 2. { 3. public: 4. //自定义类型,使用初始化列表,只需要构造函数 5. Date(int day, int hour) 6. :_t(hour) 7. { 8. _day = day; 9. } 10. 11. private: 12. int _day; 13. Time _t; 14. };
因此,建议尽量直接使用初始化列表进行初始化。
(4)成员变量初始化的顺序就是成员变量在类中的声明次序,与初始化列表中的先后次序无关。
如下代码,类成员变量中先声明了_a2,再声明了_a1,因此初始化的顺序是先初始化_a2,再初始化_a1:
1. #include <iostream> 2. using namespace std; 3. class A 4. { 5. public: 6. A(int a) 7. : _a1(a) 8. , _a2(_a1) 9. {} 10. 11. void Print() 12. { 13. cout << _a1 << " " << _a2 << endl; 14. } 15. private: 16. int _a2;//先声明_a2 17. int _a1;//后声明_a1 18. }; 19. 20. int main() { 21. A aa(1); 22. aa.Print(); 23. }
先声明_a2就会先初始化_a2,用_a1初始化_a2,由于此时_a1还是随机值,因此_a2的值也是随机值,_a1使用a的值1进行初始化,因此,_a1的值为1:
所以,建议类中的成员变量声明的顺序和初始化列表中初始化的顺序一致。