• C++的类与对象(三):构造函数、析构函数、对象的销毁顺序


    目录

    类的6个默认成员函数

    构造函数

    语法

    特性

    析构函数

    特性

    对象的销毁顺序​​​​​​​​​​​​​​


    类的6个默认成员函数

    问题:一个什么成员都没的类叫做空类,空类中真的什么都没有吗?

    基本概念:任何类在什么都不写时,编译器会自动生成以下六个默认成员函数(无参的)

    定义:用户没有显式实现,编译器会生成的成员函数称为默认成员函数

    注意事项:如果我们自己实现了这些函数,那么编译器就不会生成默认的成员函数 

    构造函数

    产生原因:初始化容易被遗忘,且有时候会太麻烦

    作用:完成初始化工作(Init)

    1. class Date
    2. {
    3. public:
    4. void Init(int year, int month, int day)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. void Print()
    11. {
    12. cout << _year << "-" << _month << "-" << _day << endl;
    13. }
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. int main()
    20. {
    21. Date d1;
    22. d1.Init(2022, 7, 5);
    23. d1.Print();
    24. Date d2;
    25. d2.Init(2022, 7, 6);
    26. d2.Print();
    27. return 0;
    28. }

            对于Data类,之前我们会通过Init公有方法给对象设置日期,但每次创建对象时都需要调用该方法设置信息就显得有点麻烦,C++的构造函数就可以做到在对象创建时,就将信息设置进去

    语法

    1、构造函数是特殊的成员函数

    2、构造函数的主要任务是在类实例化对象时初始化对象,而不是去开空间创建对象

    3、构造函数的函数名与所在类的类名相同(类名是A,构造函数的名字就叫A,客随主便)

    4、构造函数无返回值(不是void,而是连void都没)

    5、对象实例化时编译器会自动调用合适的构造函数(对象后没括号就调无参构造函数,有实参就调用带参构造函数)

    1. #include
    2. using namespace std;
    3.  class Date
    4. {
    5.  public:
    6.      // 1.无参构造函数
    7.      Date()
    8.     {
    9. _year = 1;
    10. _month = 1;
    11. _day = 1;
    12. }
    13.   void Print()
    14. {
    15. cout << _year << "-" << _month << "-" << _day << endl;
    16. }
    17.  private:
    18.      int _year;
    19.      int _month;
    20.      int _day;
    21. };
    22. int main()
    23. {
    24.   Date d1; // 调用无参构造函数
    25.    d1.Print();
    26. return 0;
    27. }

    6、构造函数可以重载

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. // 1.无参构造函数
    7. Date()
    8. {
    9. _year = 1;
    10. _month = 1;
    11. _day = 1;
    12. }
    13. void Print()
    14. {
    15. cout << _year << "-" << _month << "-" << _day << endl;
    16. }
    17. // 2.带参构造函数
    18. Date(int year, int month, int day)
    19. {
    20. _year = year;
    21. _month = month;
    22. _day = day;
    23. }
    24. private:
    25. int _year;
    26. int _month;
    27. int _day;
    28. };
    29. int main()
    30. {
    31. Date d1; // 调用无参构造函数
    32. d1.Print();
    33. Date d2(2015, 1, 1); // 调用带参的构造函数
    34. d2.Print();
    35. return 0;
    36. }

    7、构造参数没有形参时,对象实例化时不能在对象后加括号

    8、 对象实例化时必须调用构造函数,没有或者没有合适的构造函数就会报错(d1原本想要调用无参的构造函数,但是只有带参的构造函数,有了带参的构造参数编译器就算像提供不带参的默认构造参数也没办法了)

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. void Print()
    7. {
    8. cout << _year << "-" << _month << "-" << _day << endl;
    9. }
    10. //带参构造函数
    11. Date(int year, int month, int day)
    12. {
    13. _year = year;
    14. _month = month;
    15. _day = day;
    16. }
    17. private:
    18. int _year;
    19. int _month;
    20. int _day;
    21. };
    22. int main()
    23. {
    24. Date d1;
    25. d1.Print();
    26. return 0;
    27. }

    9、无参的构造函数和全缺省的构造函数在语法上因为函数重载可以同时存在,但是在实践中不可以,会造成调用歧义

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date()
    7. {
    8. _year = 1;
    9. _month = 1;
    10. _day = 1;
    11. }
    12. void Print()
    13. {
    14. cout << _year << "-" << _month << "-" << _day << endl;
    15. }
    16. Date(int year = 1, int month = 1, int day = 1)
    17. {
    18. _year = year;
    19. _month = month;
    20. _day = day;
    21. }
    22. private:
    23. int _year;
    24. int _month;
    25. int _day;
    26. };
    27. int main()
    28. {
    29. Date d1;
    30. d1.Print();
    31. return 0;
    32. }

    特性

    1(重点)、实例化对象时我们不写构造函数(包括默认构造函数和普通的构造函数),编译器就会生成默认构造函数,从而初始化对象,我们写了构造参数编译器就不会生成默认构造参数(普通的构造参数是指带参的构造参数)

    2、编译器生成的默认构造参数什么也不干,为什么?(我们程序员没有定义一个构造参数是我们的问题,但是你编译器既然为我们自动生成了一个默认构造参数,你总得让它起点作用吧,就是只让成员变量初始化为0也行啊)

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. void Print()
    7. {
    8. cout << _year << "-" << _month << "-" << _day << endl;
    9. }
    10. private:
    11. int _year;
    12. int _month;
    13. int _day;
    14. };
    15. int main()
    16. {
    17. Date d1;
    18. d1.Print();
    19. return 0;
    20. }

    这是因为,C++将数据分为了内置(基本)类型和自定义类型:

    • 内置类型:语言自身定义的类型(int、char、double、任何类型的指针等)
    • 自定义类型:程序员根据自己需求定义的类型(struct、class等)

    3、C++98规定编译器生成的默认构造函数(程序员没提过时),对内置类型不做处理,对自定义类型会去调用它的!!默认构造函数!!(一定是默认构造函数而不是普通的构造函数)

    1. #include
    2. using namespace std;
    3. //编译器对自定义类型会调用它的默认构造
    4. class A
    5. {
    6. public:
    7. A()
    8. {
    9. cout << "A()" << endl;
    10. _a = 0;
    11. }
    12. private:
    13. int _a;
    14. };
    15. //编译器对内置类型不做处理
    16. class Date
    17. {
    18. public:
    19. //这里没有自定义构造函数,编译器默认生成构成函数
    20. void Print()
    21. {
    22. cout << _year << "-" << _month << "-" << _day << endl;
    23. }
    24. private:
    25. int _year;
    26. int _month;
    27. int _day;
    28. A _aa;
    29. };
    30. int main()
    31. {
    32. Date d1;
    33. d1.Print();
    34. return 0;
    35. }

    大致步骤如下:
    ①Date d1:实例化对象d1

    ②Data{}:编译器自动生成默认构造函数,并开始对成员变量开始初始化 

    ③__aa:_aa的类型是A类,属于自定义类型,调用A类的提供的!默认!构造函数

    如果A类也没提供默认构造函数,那么_aa也是随机值(虽然编译器也可以为A提供但是没用):

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. int getA() // 添加一个公共方法以获取 _a 的值
    7. {
    8. return _a;
    9. }
    10. private:
    11. int _a;
    12. };
    13. class Date
    14. {
    15. public:
    16. void Print()
    17. {
    18. cout << "Year: " << _year << ", Month: " << _month << ", Day: " << _day << endl;
    19. cout << "Value of '_aa' member variable in class 'Date': " << _aa.getA() << endl; // 打印_aa对象内部_a变量的值
    20. }
    21. private:
    22. int _year;
    23. int _month;
    24. int _day;
    25. A _aa;
    26. };
    27. int main()
    28. {
    29. Date d1;
    30. d1.Print();
    31. return 0;
    32. }

    ④_year、_month、_day:是int类型,属于内置类型,不做任何操作,结果为随机值

    为什么在调试时,先初始化对象_aa?

    答:C++中,类的成员变量初始化顺序是由它们在类中声明的顺序决定的,而不是由它们在构造函数初始化列表中出现的顺序决定

    注意事项:有些新的编译器会对内置类型也做处理,但是C++的标准没有规定 

    4、C++11规定内置类型的成员变量声明时可给默认值(为C++98打补丁,没给的依然是随机值)

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. A()
    7. {
    8. cout << "A()" << endl;
    9. _a = 0;
    10. }
    11. private:
    12. int _a;
    13. };
    14. class Date
    15. {
    16. public:
    17. void Print()
    18. {
    19. cout << _year << "-" << _month << "-" << _day << endl;
    20. }
    21. private:
    22. //声明时给缺省值
    23. int _year = 2024;
    24. int _month =3 ;
    25. int _day;
    26. A _aa;
    27. };
    28. int main()
    29. {
    30. Date d1;
    31. d1.Print();
    32. return 0;
    33. }

    5、 只有无参 / 全缺省构造函数、编译器默认生成的构造函数都叫默认构造函数,其余的都是构造函数而不是默认构造函数,默认构造函数只能有一个 

    1. //默认构造参数只能有一个:
    2. #include
    3. using namespace std;
    4. class Date
    5. {
    6. public:
    7. //无参的构造函数是默认构造函数
    8. Date()
    9. {
    10. _year = 1900;
    11. _month = 1;
    12. _day = 1;
    13. }
    14. //全缺省的构造函数是默认构造函数
    15. Date(int year = 1900, int month = 1, int day = 1)
    16. {
    17. _year = year;
    18. _month = month;
    19. _day = day;
    20. }
    21. private:
    22. int _year;
    23. int _month;
    24. int _day;
    25. };
    26. int main()
    27. {
    28. Date d1;
    29. return 0;
    30. }

    1. //成员变量在声明时未给缺省值
    2. #include
    3. using namespace std;
    4. class Date
    5. {
    6. public:
    7. Date(int year = 1, int month , int day)
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }
    13. void Print()
    14. {
    15. cout << _year << "-" << _month << "-" << _day << endl;
    16. }
    17. ~Date()
    18. {
    19. cout << this << endl;
    20. cout << " ~Date()" << endl;
    21. }
    22. private:
    23. int _year;
    24. int _month;
    25. int _day;
    26. };
    27. int main()
    28. {
    29. Date d1;
    30. d1.Print();
    31. return 0;
    32. }

    1. //成员变量在声明时提供缺省值:
    2. #include
    3. using namespace std;
    4. class Date
    5. {
    6. public:
    7. Date(int year = 1, int month , int day)
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }
    13. void Print()
    14. {
    15. cout << _year << "-" << _month << "-" << _day << endl;
    16. }
    17. ~Date()
    18. {
    19. cout << this << endl;
    20. cout << " ~Date()" << endl;
    21. }
    22. private:
    23. int _year = 1;
    24. int _month = 1;
    25. int _day =1;
    26. };
    27. int main()
    28. {
    29. Date d1;
    30. d1.Print();
    31. return 0;
    32. }

    为什么成员变量在声明时已经给了缺省值,为什么Data还要再给?

    虽然为 Date 类中的私有成员变量 _year_month, 和 _day 提供了默认值(1, 1, 1),但这些缺省值仅在默认构造函数未初始化成员变量时才会被用于初始化成员变量。实例化d1时,类中定义了一个貌似是全缺省的构造函数,因此编译器不会自动生成默认构造函数(那个位置上已经有人了,只是那个人有点缺陷),且因为该貌似的全缺省构造函数缺少两个缺省值所以报错

    1. #include
    2. using namespace std;
    3. class Time
    4. {
    5. public:
    6. //带参构造函数,不是默认构造函数
    7. Time(int hour)
    8. {
    9. cout << "Time()" << endl;
    10. _hour = 0;
    11. _minute = 0;
    12. _second = 0;
    13. }
    14. private:
    15. int _hour;
    16. int _minute;
    17. int _second;
    18. };
    19. class Date
    20. {
    21. private:
    22. //声明给缺省值
    23. int _year;
    24. int _month;
    25. int _day;
    26. Time _t;
    27. };
    28. int main()
    29. {
    30. Date d;
    31. return 0;
    32. }

    为什么会提示_t没有默认构造函数?

    答:实例化对象d时,Date类没给构造函数,编译器自动生成默认构造函数(无参),_year、_month、_day是内置类型不做处理,_t是自定义类型会调用Time类的默认构造函数,但是Time类中已经有了一个带参的构造函数(普通构造函数)所以Time类中不会有默认构造函数了(只要我们写了构造函数,即使它是带参的普通构造函数,编译器就不会给我们提供默认构造参数了)故报错

     结论:绝大多数场景下都需要自己实现构造函数,且更推荐用全缺省构造函数​​​​​​​

    析构函数

     产生原因:需要人为销毁的空间容易忘记销毁,内存泄漏可能不会报错

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. void Print()
    7. {
    8. cout << _year << "-" << _month << "-" << _day << endl;
    9. }
    10. ~Date()
    11. {
    12. cout << this << endl;
    13. cout << " ~Date()" << endl;
    14. }
    15. private:
    16. //声明给缺省值
    17. int _year = 1;
    18. int _month = 1;
    19. int _day = 1;
    20. };
    21. void func()
    22. {
    23. Date d2;
    24. }
    25. class Stack
    26. {
    27. public:
    28. Stack(size_t capacity = 4)
    29. {
    30. _array = (int*)malloc(sizeof(int*) * capacity);
    31. if (NULL == _array)
    32. {
    33. perror("malloc申请空间失败!!!");
    34. return;
    35. }
    36. _capacity = capacity;
    37. _size = 0;
    38. }
    39. void Push(int data)
    40. {
    41. // CheckCapacity();
    42. _array[_size] = data;
    43. _size++;
    44. }
    45. //~Stack()
    46. //{
    47. // cout << "~Stack()" << endl;
    48. // if (_array)
    49. // {
    50. // free(_array);
    51. // _array = nullptr;
    52. // }
    53. // _size = _capacity = 0;
    54. //}
    55. private:
    56. int* _array;
    57. int _capacity;
    58. int _size;
    59. };
    60. int main()
    61. {
    62. func();
    63. Date d1;
    64. Stack st1; //内存泄漏
    65. return 0;
    66. }

            如果Stack类中没有自定义的析构函数就会出现内存泄漏,因为我们即没有定义Destory函数去销毁在堆上开辟的空间,析构函数也不起作用

    作用:完成清理工作(Destory)

    基本概念:析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

    特性

    1、析构函数名是在类名前加上字符~

    2、析构函数无参数无返回值类型

    3、一个类只能有一个析构函数,若未显示定义,编译器会自动生成默认的析构函数

    4、析构函数不能重载(清理一遍数据就应该被清理完成)

    5、对象生命周期结束(函数结束)时,程序会自动调用析构函数

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. ~Date()
    7. {
    8. cout << this << endl;
    9. cout << " ~Date()" << endl;
    10. }
    11. private:
    12. //声明给缺省值
    13. int _year = 1;
    14. int _month = 1;
    15. int _day =1;
    16. };
    17. void func()
    18. {
    19. Date d2;//实例化对象d2
    20. }//函数结束,调用析构函数销毁数据
    21. int main()
    22. {
    23. func();
    24. Date d1;//实例化对象d1
    25. return 0;
    26. }//函数结束,调用析构函数销毁数据

    6、编译器生成的析构函数,对内置成员变量不做处理,对自定成员变量会调用它的析构函数

    1. #include
    2. using namespace std;
    3. class Time
    4. {
    5. public:
    6. ~Time()
    7. {
    8. cout << "~Time()" << endl;
    9. }
    10. private:
    11. int _hour;
    12. int _minute;
    13. int _second;
    14. };
    15. class Date
    16. {
    17. private:
    18. // 基本类型(内置类型)
    19. int _year = 1970;
    20. int _month = 1;
    21. int _day = 1;
    22. // 自定义类型
    23. Time _t;
    24. };
    25. int main()
    26. {
    27. Date d;
    28. return 0;
    29. }

    在main函数中没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

    答:main函数中创建了Date类的对象d,d中包含了四个成员变量,其中_year、_month、_day是内置类型的成员变量,销毁时不需要人为清理资源main函数结束后系统直接回收,而_t是Time类的对象,为了销毁_t就需要Data类的析构函数,但是我们并没有给Date类定义析构函数,所以编译器就会为Date类提供一个默认析构函数,该函数对于自定义类型的成员变量就会调用它(_t)的析构函数(Time提供的析构函数)

    7、后定义的先析构(定义->开辟帧栈->压栈,栈后进先出)

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. ~Date()
    7. {
    8. cout << this << endl;
    9. cout << " ~Date()" << endl;
    10. }
    11. private:
    12. //声明给缺省值
    13. int _year = 1;
    14. int _month = 1;
    15. int _day = 1;
    16. };
    17. class Stack
    18. {
    19. public:
    20. Stack(int capacity = 4)
    21. {
    22. _array = (int*)malloc(sizeof(int*) * capacity);
    23. if (NULL == _array)
    24. {
    25. perror("malloc申请空间失败!!!");
    26. return;
    27. }
    28. _capacity = capacity;
    29. _size = 0;
    30. }
    31. void Push(int data)
    32. {
    33. // CheckCapacity();
    34. _array[_size] = data;
    35. _size++;
    36. }
    37. ~Stack()
    38. {
    39. cout << this << endl;
    40. cout << "~Stack()" << endl;
    41. if (_array)
    42. {
    43. free(_array);
    44. _array = nullptr;
    45. }
    46. _size = _capacity = 0;
    47. }
    48. private:
    49. int* _array;
    50. int _capacity;
    51. int _size;
    52. };
    53. int main()
    54. {
    55. Date d1;
    56. Stack st1;
    57. return 0;
    58. }

     8、如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数(Date类);有资源申请时,一定要写,否则会造成资源泄漏(Stack类)

     9、自定义类型的本质还是内置类型

    对象的销毁顺序

    从左至右:局部变量(后定义的先析构)——>局部的静态——>全局(后定义的先析构)

    当成栈来理解,先进后出 

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. //全缺省构造函数
    7. Date(int year = 1)
    8. {
    9. _year = year;
    10. }
    11. ~Date()
    12. {
    13. cout << " ~Date()->" << _year << endl;
    14. }
    15. private:
    16. //声明给缺省值
    17. int _year;
    18. int _month;
    19. int _day;
    20. };
    21. Date d5(5);//全局
    22. Date d6(6);//全局
    23. static Date d7(7);//全局
    24. static Date d8(8);//全局
    25. void func()
    26. {
    27. Date d3(3);//局部
    28. static Date d4(4);//局部的静态
    29. }
    30. int main()
    31. {
    32. Date d1(1);//局部
    33. Date d2(2);//局部
    34. func();
    35. return 0;
    36. }

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. //全缺省构造函数
    7. Date(int year = 1)
    8. {
    9. _year = year;
    10. }
    11. ~Date()
    12. {
    13. cout << " ~Date()->" << _year << endl;
    14. }
    15. private:
    16. int _year;
    17. int _month;
    18. int _day;
    19. };
    20. int main()
    21. {
    22. Date d1(1);//局部
    23. Date d2(2);//局部
    24. static Date d3(3);//局部的静态
    25. return 0;
    26. }

    ~over~

  • 相关阅读:
    取代 C++,Google 强势开源 Carbon语言
    【Java实战】大厂都是怎样进行单元测试的
    Java 网络编程 —— 创建非阻塞的 HTTP 服务器
    护眼灯显色指数应达多少?适合学生的护眼台灯推荐
    【正点原子I.MX6U-MINI应用篇】9、嵌入式Linux中的多线程编程pthread
    Chrome 配置samesite=none方式
    matlab画图常用函数image、imagesc、imshow区别
    Flume系列:Flume通道拓扑结构
    探索Semantic Kernel内置插件:深入了解HttpPlugin的应用
    人工智能基础_机器学习020_归一化实战_天池工业蒸汽量项目归一化实战过程---人工智能工作笔记0060
  • 原文地址:https://blog.csdn.net/m0_73975164/article/details/136487776