• C++类和对象(二)构造函数、析构函数、拷贝构造函数


    目录

    1.类的6个默认成员函数

    2. 构造函数

    2.1 概念

    2.2 特性

    3.析构函数

    3.1 概念

    3.2 特性

    4. 拷贝构造函数

    4.1 概念

    4.2 特征

    总结代码


    1.类的6个默认成员函数

    如果一个类中什么成员都没有,简称为空类。
    空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下 6 个默认成员函数。
    默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
    class Date {}; //空类

    2. 构造函数

    2.1 概念

    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. }
    构造函数 是一个 特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,以保证每个数据成 员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

    2.2 特性

    构造函数 是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务 并不是开空间创建对象,而是初始化对象
    其特征如下:
    1. 函数名与类名相同。
    2. 无返回值。
    3. 对象实例化时编译器 自动调用 对应的构造函数。
    4. 构造函数可以重载
    5. 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
    1. class Date
    2. {
    3. public:
    4. // 1.无参构造函数
    5. Date()
    6. {}
    7. // 2.带参构造函数
    8. Date(int year, int month, int day)
    9. {
    10. _year = year;
    11. _month = month;
    12. _day = day;
    13. }
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. void TestDate()
    20. {
    21. Date d1; // 调用无参构造函数
    22. Date d2(2015, 1, 1); // 调用带参的构造函数
    23. // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    24. // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    25. // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
    26. Date d3();
    27. }
           C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:
    int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

    注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在类中声明时 可以给默认值
    1. class Date
    2. {
    3. private:
    4. // 基本类型(内置类型)
    5. int _year = 1970;
    6. int _month = 1;
    7. int _day = 1;
    8. // 自定义类型
    9. Time _t;
    10. };
    无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
    注意:无参 构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数
    因为你不传参,我另一个这边有默认值,两个有一个就能完成工作,如下代码:全缺省的已经可以包含无参的功能,两个都在的话,创建对象可能出现问题
    1. class Date
    2. {
    3. public:
    4. //无参的构造函数
    5. Date()
    6. {
    7. _year = 1900;
    8. _month = 1;
    9. _day = 1;
    10. }
    11. //全缺省的构造函数
    12. Date(int year = 1900, int month = 1, int day = 1)
    13. {
    14. _year = year;
    15. _month = month;
    16. _day = day;
    17. }
    18. private:
    19. int _year;
    20. int _month;
    21. int _day;
    22. };

    3.析构函数

    3.1 概念

    通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
    析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

    3.2 特性

    析构函数是特殊的成员函数,其特征如下:

    1. 析构函数名是在类名前加上字符 ~
    2. 无参数无返回值类型。
    3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
    4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数

    以下为栈的实现

    1. #include
    2. using namespace std;
    3. #include
    4. #include
    5. typedef int DataType;
    6. struct Stack
    7. {
    8. public:
    9. void Init()
    10. {
    11. _array = (DataType*)malloc(10 * sizeof(DataType));
    12. if (NULL == _array)
    13. {
    14. assert(false);
    15. return;
    16. }
    17. //没上面问题容易给3,数量为0
    18. _capacity = 10;
    19. _size = 0;
    20. }
    21. //入栈
    22. void Push(DataType data)
    23. {
    24. _array[_size] = data;
    25. ++_size;
    26. }
    27. //出战
    28. void Pop()
    29. {
    30. if (Empty())
    31. return;
    32. _size--;
    33. }
    34. //获取栈顶元素
    35. DataType Top()
    36. {
    37. assert(!Empty());
    38. return _array[_size - 1];
    39. }
    40. bool Empty()
    41. {
    42. return 0 == _size;
    43. }
    44. int Size()
    45. {
    46. return _size;
    47. }
    48. void Destroy()
    49. {
    50. if (_array)
    51. {
    52. free(_array);
    53. _array = nullptr;
    54. _capacity = 0;
    55. _size = 0;
    56. }
    57. }
    58. private:
    59. //扩容方法
    60. void _CheckCapacity()
    61. {
    62. }
    63. private:
    64. DataType* _array;
    65. size_t _capacity;
    66. size_t _size;
    67. };
    68. int main()
    69. {
    70. Stack s;
    71. s.Init();
    72. s.Push(1);
    73. s.Push(2);
    74. s.Push(3);
    75. s.Push(4);
    76. s.Push(5);
    77. cout << s.Top() << endl;
    78. cout << s.Size() << endl;
    79. s.Pop();
    80. s.Pop();
    81. cout << s.Top() << endl;
    82. cout << s.Size() << endl;
    83. s.Destroy();
    84. return 0;
    85. }

    因为我们代码中自己写了初始化 Init 和 Destory,所以在主函数中你要进行栈操作,必须手动加上s.Init();以及s.Destroy(); 要是不加就会崩溃,这样的话你总有失误的时候,此时我们想让栈创建好就有空间,所以对代码需要优化,下面为部分优化代码

    1. ~Stack()
    2. {
    3. if (_array)
    4. {
    5. free(_array);
    6. _array = nullptr;
    7. _capacity = 0;
    8. _size = 0;
    9. }
    10. }
    11. void TestStack()
    12. {
    13. Stack s;
    14. s.Push(1);
    15. s.Push(2);
    16. s.Push(3);
    17. s.Push(4);
    18. s.Push(66);
    19. cout << s.Top() << endl;
    20. cout << s.Size() << endl;
    21. s.Pop();
    22. s.Pop();
    23. cout << s.Top() << endl;
    24. cout << s.Size() << endl;
    25. }
    26. int main()
    27. {
    28. TestStack();
    29. return 0;
    30. }
    6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date 类; 有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

    注意:像Date类一样,对象中没有涉及到任何资源管理时,该类的析构函数可以不必给出

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

    4. 拷贝构造函数

    4.1 概念

    拷贝构造函数 只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存在的类类型 对象创建新对象时由编译器自动调用

    4.2 特征

    拷贝构造函数也是特殊的成员函数,其 特征 如下
    1. 拷贝构造函数 是构造函数的一个重载形式
    2. 拷贝构造函数的 参数只有一个必须是类类型对象的引用 ,使用 传值方式编译器直接报错 ,因为会引发无穷递归调用
    1. Date(const Date& d)
    2. {
    3. _year = d._year;
    4. _month = d._month;
    5. _day = d._day;
    6. }
    7. Date d2(d1);

    错误例子如下:如果这样使用,会不断的调用拷贝,这就是传值引发对象的拷贝

    Date(const Date& d)

    3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

    问题:编译器生成的拷贝构造,虽然没有生成但是可以完成拷贝构造的工作,既然编译器已经可以完成了,那拷贝构造函数还需要用户自己写吗?、

    答:像日期这种没有涉及到资源管理时, 可写可不写,一般不写,因为编译器可以完成拷贝的工作,如果需要自己再去实现,注意编译器是按照值的方式拷贝的---即:将一个对象中的内容原封不动的拷贝到另一个对象中(浅拷贝)

    注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构 造函数是一定要写的,否则就是浅拷贝

    总结代码

    对上面所有内容用一段代码进行对比总结,代码如下:

    1. #include
    2. using namespace std;
    3. #include
    4. #include
    5. class Date
    6. {
    7. public:
    8. Date(int year = 2000, int month = 1, int day = 1)
    9. {
    10. _year = year;
    11. _month = month;
    12. _day = day;
    13. cout << "Date(int,int,int):" << this << endl;
    14. }
    15. Date(const Date& d)
    16. {
    17. _year = d._year;
    18. _month = d._month;
    19. _day = d._day;
    20. cout << "Date(const Date& d):" << this << endl;
    21. }
    22. ~Date()
    23. {
    24. cout << "~Date():" << this << endl;
    25. }
    26. private:
    27. int _year;
    28. int _month;
    29. int _day;
    30. };
    31. void TestDate1()
    32. {
    33. //只要创建对象,就必须调用构造函数
    34. //拷贝构造是:用已经存在的对象构造新对象时调用
    35. //其余创建新对象的场景调用的基本都是构造函数
    36. Date d1(2022,11,15);//构造函数
    37. //用已经存在的对象创建,拷贝构造函数调用场景1
    38. Date d2(d1);
    39. }
    40. //拷贝构造函数调用场景2:以值的方式传参
    41. void TestDate2(Date d)
    42. {
    43. Date dd;
    44. }
    45. //拷贝构造函数调用场景3:以值的方式返回对象
    46. Date TestDate3()
    47. {
    48. Date d;
    49. return d;
    50. }
    51. int main()
    52. {
    53. Date md;
    54. TestDate1();
    55. TestDate2(md);
    56. TestDate3();
    57. return 0;
    58. }

    分析:按上图几行来说明

    第一,二行首先就是运行TestDate1 ,运行d1和调用Date

    第三行,运行d2,进行了拷贝构造,调用了拷贝构造函数

    第四行第五行进行析构函数销毁,因为是栈,先创建的后销毁,所以先销毁d2,在销毁d1

    第六行运行TestDate2,拷贝构造md,在运行构造函数其中的dd,之后均析构销毁

    TestDate3先构造d,在要以值的方式返回的时候,不能返回d,因为函数结束会销毁,所以只能借助拷贝构造函数在返回之前先创建临时对象,返回临时对象

    注意:

    // 1. 以值的方式返回时,如果返回的是匿名对象,则编译器不会再用匿名对象

    // 拷贝构造临时对象,而是直接将匿名对象返回了

    // 匿名对象:没有名字的对象

    // 2. 如果参数是以值的方式传递,实参如果也是匿名对象,也会少一次拷贝构造

    // 3. 在C++中,参数能使用引用尽量使用引用,如果不想通过形参改变外部的实参,尽量设置为const类型的引用

     

  • 相关阅读:
    神经网络解决非线性问题,非线性回归 神经网络
    Redis笔记
    Docker的使用
    JUC并发编程学习笔记(四)8锁现象
    李宏毅机器学习笔记-transformer
    外汇天眼:FXOpen宣布降低40%点差,对交易者来说是利好么?
    自签名SSL证书的安全隐患和风险有哪些?
    学习尚硅谷HTML+CSS总结
    【无标题】
    91183-98-1,UDP-N-acetylglucosamine,5′-二磷酸尿嘧啶核苷-N-乙酰半乳糖胺二钠盐
  • 原文地址:https://blog.csdn.net/weixin_59215611/article/details/128049263