假如一个类中既没有成员变量也没有成员函数,那么这个类就是空类,空类并不是什么都没有,因为所有类都会生成如下6个默认成员函数:
一、构造函数
对于日期类对象,我们可能会忘记调用Init函数进行初始化,C++为了解决这个问题,引入构造函数来进行初始化。
1. #include<iostream> 2. using namespace std; 3. 4. class Date 5. { 6. public: 7. void Init(int year, int month, int day) 8. { 9. _year = year; 10. _month = month; 11. _day = day; 12. cout << "this:" << this << endl; 13. } 14. 15. private: 16. int _year; 17. int _month; 18. int _day; 19. }; 20. 21. int main() 22. { 23. Date d1; 24. d1.Init(2022, 4, 8);//如果忘记调用Init函数,d1就不不会被初始化 25. 26. return 0; 27. }
构造函数是一个特殊的成员函数,名字与类名相同,编译器创建类类型对象时会自动调用构造函数,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。主要任务是初始化对象,而不是开空间创建对象。构造函数最主要的特点是能够自动调用。
特性:
(1)函数名与类名相同。
(2)无返回值。
(3)对象实例化时编译器自动调用对应的构造函数。
(4)构造函数可以重载。
(5)如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义了编译器将不再生成。
默认构造函数是不用传参就可以调用的构造函数,分为:
(1)我们没写,编译器默认自动生成
(2)我们写的无参默认构造函数
(3)我们写的带参全缺省默认构造函数
这3种默认构造函数只能有一个,(2)和(3)不能同时存在的原因:当定义一个不带参数的类对象时,编译器不能确定到底要调用我们写的无参默认构造函数还是要调用我们写的带参全缺省默认构造函数,会报“对重载函数的调用不明确错误”。
(1)我们没写,编译器默认自动生成
1. #include<iostream> 2. using namespace std; 3. 4. class Date 5. { 6. private: 7. int _year; 8. int _month; 9. int _day; 10. }; 11. 12. int main() 13. { 14. Date d1;//调用编译器自动生成的默认构造函数 15. 16. return 0; 17. }
只不过d1的3个成员变量都是随机值:
(2)我们写的无参构造函数
1. #include<iostream> 2. using namespace std; 3. 4. class Date 5. { 6. public: 7. 8. //1.无参默认构造函数:初始化对象 9. Date() 10. { 11. _year = 2022; 12. _month = 4; 13. _day = 8; 14. } 15. 16. private: 17. int _year; 18. int _month; 19. int _day; 20. }; 21. 22. int main() 23. { 24. Date d1; 25. 26. return 0; 27. }
(3)带参默认构造函数
1. #include<iostream> 2. using namespace std; 3. 4. class Date 5. { 6. public: 7. //2.带参全缺省默认构造函数:初始化对象 8. Date(int year = 2022, int month = 4, int day= 8) 9. { 10. _year = year; 11. _month = month; 12. _day = day; 13. } 14. 15. private: 16. int _year; 17. int _month; 18. int _day; 19. }; 20. 21. int main() 22. { 23. Date d1;//调用带参默认构造函数 24. 25. return 0; 26. }
一般情况下会把无参默认构造函数和带参默认构造函数合二为一,因为这样能适应各种场景:
1. #include<iostream> 2. using namespace std; 3. 4. class Date 5. { 6. public: 7. //无参带参二合一默认构造函数:初始化对象 8. Date(int year = 2022, int month = 4, int day = 8) 9. { 10. _year = year; 11. _month = month; 12. _day = day; 13. } 14. 15. private: 16. int _year; 17. int _month; 18. int _day; 19. }; 20. 21. int main() 22. { 23. Date d1;//调用二合一默认构造函数 24. Date d2(2050, 9, 6);//调用二合一默认构造函数 25. Date d3(2030);//只给一个参数 26. 27. return 0; 28. }
大多数情况下,我们都需要自己写构造函数,因为默认生成的构造函数不一定好用。
在上一小节中,我们不写默认构造函数时,编译器自动生成的默认构造函数使得类对象的初始化成为随机值,初始化成随机值又没有什么用,那么编译器自动生成的默认构造函数到底有什么用呢?
C++把类型分成内置类型和自定义类型:
(1)内置类型就是语法已经定义好的类型:如int、char、long、short...等类型,编译器生成默认的构造函数对内置类型不做处理。
(2)自定义类型是使用class/struct/union自己定义的类型:而对自定义类型成员会调用的它的默认成员函数。
如下代码中,Date类没写构造函数,但会对Date自定义类型成员_t调用_t的默认成员函数,这里调用了_t的默认构造函数:
1. #include<iostream> 2. using namespace std; 3. 4. class Time 5. { 6. public: 7. Time() 8. { 9. cout << "Time()" << endl; 10. _hour = 0; 11. _minute = 0; 12. _second = 0; 13. } 14. 15. private: 16. int _hour; 17. int _minute; 18. int _second; 19. }; 20. 21. class Date 22. { 23. private: 24. // 基本类型(内置类型) 25. int _year; 26. int _month; 27. int _day; 28. 29. // 自定义类型 30. Time _t; 31. }; 32. 33. int main() 34. { 35. Date d; 36. return 0; 37. }
虽然对象d的3个成员变量_year、_month、_day被初始化成随机值
但是打印了Time( ),说明调用了Date类的自定义类型成员_t的默认成员函数Time( )。
二、析构函数
析构函数用来完成类的资源清理工作,编译器在销毁对象时,会自动调用析构函数。
特性:
(1)析构函数名是在类名前加上字符 ~。
(2)无参数无返回值。(析构函数不能重载,一个类有且仅有一个析构函数)
(3)一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
(4)对象生命周期结束时,C++编译系统系统自动调用析构函数。
什么时候对象会被销毁?对象出了定义对象的函数作用域以后,生命周期到了,对象会被销毁。
Date类不需要清理,Date类的析构函数什么也不做。年月日是属于对象的,而对象属于栈帧,当对象生命周期结束后,栈帧销毁了,对象所占用的空间就还给操作系统了。
如何证明编译器销毁Date类对象时自动调用了析构函数呢?在析构函数内部加打印,程序执行完毕后,如果编译器调用了析构函数,那么一定会打印。
1. #include<iostream> 2. using namespace std; 3. 4. class Date 5. { 6. public: 7. //无参带参二合一默认构造函数:初始化对象 8. Date(int year = 2022, int month = 4, int day = 8) 9. { 10. _year = year; 11. _month = month; 12. _day = day; 13. } 14. 15. //析构函数:清理资源 16. ~Date() 17. { 18. cout << "~Date()" << endl;//在析构函数内打印 19. } 20. 21. private: 22. int _year; 23. int _month; 24. int _day; 25. }; 26. 27. int main() 28. { 29. Date d1;//调用二合一默认构造函数 30. Date d2(2050, 9, 6);//调用二合一默认构造函数 31. Date d3(2030);//只给一个参数 32. 33. return 0; 34. }
打印了, 证明编译器销毁Date类对象时自动调用了析构函数:
既然日期类的析构函数什么也不做,那么我们自己可以不显式定义日期类的析构函数,由编译器自动生成就好了。
但是栈的析构函数就需要我们自己显式定义,它构造对象时申请了堆的空间,对象销毁时需要释放申请的堆空间,因此Stack类的析构函数中需要free:
1. #include <stdlib.h> 2. #include <iostream> 3. using namespace std; 4. 5. typedef int STDataType; 6. 7. class Stack 8. { 9. public: 10. //构造函数 11. Stack(int capacity = 4) 12. { 13. _a = (STDataType*)malloc(sizeof(STDataType) * 4); 14. _size = 0; 15. _capacity = capacity; 16. } 17. 18. //析构函数:清理资源 19. ~Stack() 20. { 21. 22. free(_a); 23. _a = nullptr; 24. _size = _capacity = 0; 25. } 26. 27. public: 28. STDataType* _a; 29. int _size; 30. int _capacity; 31. 32. }; 33. 34. int main() 35. { 36. 37. Stack st; //定义一个对象 38. 39. return 0; 40. }
st有两块空间要销毁:
①st对象本身,函数结束,栈帧销毁,st对象就销毁
②st对象里面的_str 指向的堆上的空间,是析构函数清理的
假如类中没有析构函数,需要写destroy函数,假如忘记调用destroy函数,会造成内存泄漏,并且不会反馈出来,不易察觉。C++很重视内存泄漏,因此将析构函数作为默认成员函数。有了析构函数之后就可以不用写destroy函数了,也就不需要考虑是否忘记调用destroy函数。
假如这个类有多个对象,那么析构的先后顺序是什么?
1. #include <stdlib.h> 2. #include <iostream> 3. using namespace std; 4. 5. typedef int STDataType; 6. 7. class Stack 8. { 9. public: 10. //构造函数 11. Stack(int capacity = 4) 12. { 13. _a = (STDataType*)malloc(sizeof(STDataType) * 4); 14. _size = 0; 15. _capacity = capacity; 16. } 17. 18. //析构函数:清理资源 19. ~Stack() 20. { 21. cout << this << endl; 22. free(_a); 23. _a = nullptr; 24. _size = _capacity = 0; 25. } 26. 27. private: 28. STDataType* _a; 29. int _size; 30. int _capacity; 31. 32. }; 33. 34. int main() 35. { 36. 37. Stack st1; //定义一个对象st1 38. cout << "&st1:"<< & st1 << endl; 39. 40. Stack st2; //定义一个对象st2 41. cout << "&st2:" << &st2 << endl; 42. 43. return 0; 44. }
发现调用析构函数时,先打印的是st2的地址,说明后创建的对象先析构。
因为对象时定义在函数中的,函数调用会建立栈帧,因此栈帧中的对象构造和析构顺序也要遵循后进先出的原则。
当不写析构函数时,编译器会自动生成默认的析构函数,不过这个默认的析构函数什么也不做,不需要清理资源。那么编译器自动生成的默认析构函数到底有什么用呢?
同析构函数
(1)对于内置类型,不会处理
(2)对于自定义类型,会调用它的析构函数
如下代码, Date类没写析构函数,但会对Date自定义类型成员_t调用_t的默认成员函数,这里调用了_t的默认析构函数:
1. #include<iostream> 2. using namespace std; 3. 4. class Time 5. { 6. public: 7. Time() 8. { 9. _hour = 0; 10. _minute = 0; 11. _second = 0; 12. } 13. 14. ~Time() 15. { 16. cout << "~Time()" << endl; 17. } 18. 19. private: 20. int _hour; 21. int _minute; 22. int _second; 23. }; 24. 25. class Date 26. { 27. private: 28. // 基本类型(内置类型) 29. int _year; 30. int _month; 31. int _day; 32. 33. // 自定义类型 34. Time _t; 35. }; 36. 37. int main() 38. { 39. Date d; 40. return 0; 41. }