• (c++)类和对象中篇


    目录

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

    2. 构造函数

    3. 析构函数

    4. 拷贝构造函数

    5. 赋值运算符重载

    6. const成员函数

    7. 取地址及const取地址操作符重载


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


    2. 构造函数
    2.1 概念

    对于以下 Date 类:
    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(2023, 9, 1);
    23. d1.Print();
    24. Date d2;
    25. d2.Init(2023, 9, 1);
    26. d2.Print();
    27. return 0;
    28. }
    29.  

              对于 Date 类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

    2.2 特性
          构造函数 是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

    其特征如下:
    • 1.函数名与类名相同。
    • 2.无返回值。
    • 3.对象实例化时编译器自动调用对应的构造函数。
    • 4.构造函数可以重载。
    1.  class Date
    2. {
    3.  public:
    4.      // 1.无参构造函数
    5.      Date()
    6.     {}
    7.  
    8.      // 2.带参构造函数
    9.      Date(int year, int month, int day)
    10.     {
    11.          _year = year;
    12.          _month = month;
    13.          _day = day;
    14.     }
    15.  private:
    16.      int _year;
    17.      int _month;
    18.      int _day;
    19. };
    20.  
    21.  void TestDate()
    22. {
    23.      Date d1; // 调用无参构造函数
    24.      Date d2(2023, 9, 1); // 调用带参的构造函数
    25.  
    26.      // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    27.      // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    28.      // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
    29.      Date d3();
    30. }
    • 5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。

    1.  class Date
    2. {
    3.  public:
    4. /*
    5. // 如果用户显式定义了构造函数,编译器将不再生成
    6. Date(int year, int month, int day)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. */
    13. void Print()
    14. {
    15. cout << _year << "-" << _month << "-" << _day << endl;
    16. }
    17.  
    18.  private:
    19. int _year;
    20. int _month;
    21. int _day;
    22. };
    23.  
    24.  int main()
    25. {
    26. //Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函
    27. //Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再
    28. 生成
    29.      // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
    30. Date d1;
    31. return 0;
    32. }
    • 6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
    • C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型.

    默认生成的构造函数:

    • 内置类型成员不做处理
    • 自定义类型成员会去调用它的默认构造函数

    验证:

    1. class Time
    2. {
    3. public:
    4. Time()
    5. {
    6. cout << "Time()" << endl;
    7. _hour = 0;
    8. _minute = 0;
    9. _second = 0;
    10. }
    11. private:
    12. int _hour;
    13. int _minute;
    14. int _second;
    15. };
    16. class Date
    17. {
    18. private:
    19. // 基本类型(内置类型)
    20. int _year;
    21. int _month;
    22. int _day;
    23. // 自定义类型
    24. Time _t;
    25. };
    26. int main()
    27. {
    28. Date d;
    29. return 0;
    30. }
    注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在 类中声明时可以给默认值

    1. class Time
    2. {
    3. public:
    4. Time()
    5. {
    6. cout << "Time()" << endl;
    7. _hour = 0;
    8. _minute = 0;
    9. _second = 0;
    10. }
    11. private:
    12. int _hour;
    13. int _minute;
    14. int _second;
    15. };
    16. class Date
    17. {
    18. private:
    19. // 基本类型(内置类型)
    20. int _year = 1970; //c++11允许的补丁
    21. int _month = 1; //这里不是初始化,而是声明,给缺省值
    22. int _day = 1; //给的是默认构造函数的缺省值
    23. // 自定义类型
    24. Time _t;
    25. };
    26. int main()
    27. {
    28. Date d;
    29. return 0;
    30. }
    • 7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
    • 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。


    3. 析构函数

    3.1 概念
    析构函数:
    •          与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
    3.2 特性
    析构函数 是特殊的成员函数,其 特征 如下:
    • 析构函数名是在类名前加上字符 ~
    • 无参数无返回值类型。
    • 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
    • 对象生命周期结束时,C++编译系统系统自动调用析构函数
    • 编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
    • 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

    栈:

    1. typedef int DataType;
    2. class Stack
    3. {
    4. public:
    5. Stack(size_t capacity = 3)
    6. {
    7. _array = (DataType*)malloc(sizeof(DataType) * capacity);
    8. if (NULL == _array)
    9. {
    10. perror("malloc申请空间失败!!!");
    11. return;
    12. }
    13. _capacity = capacity;
    14. _size = 0;
    15. }
    16. void Push(DataType data)
    17. {
    18. //...
    19. _array[_size] = data;
    20. _size++;
    21. }
    22. ~Stack()
    23. {
    24. free(_array);
    25. //_array = nullptr;
    26. //_capacity = _size = 0;
    27. }
    28. private:
    29. DataType* _array;
    30. int _capacity;
    31. int _size;
    32. };

    日期类对象:

    1. class Date
    2. {
    3. public:
    4. Date(int year = 2023, int month = 9, int day = 1)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. //~Date()
    11. //{
    12. // //~Date()没有什么需要清理的
    13. // cout << "~Date()" << endl;
    14. //}
    15. //内置类型析构函数不做处理
    16. private:
    17. int _year;
    18. int _month;
    19. int _day;
    20. };

    自定义队列:

    1. class MyQueue
    2. {
    3. public:
    4. //...
    5. private:
    6. size_t _szie = 0;
    7. Stack _st1;
    8. Stack _st2;
    9. };

    关于以上三个函数的调用:

    由此也可以总结出析构函数的特点:

    a.内置类型不做处理

    b.自定义类型成员会去调用它的析构函数


    4. 拷贝构造函数

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

    特征
    拷贝构造函数也是特殊的成员函数,其 特征 如下:
    • 1. 拷贝构造函数是构造函数的一个重载形式
    • 2. 拷贝构造函数的参数只有一个必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
    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.    Date(const Date& d)   //拷贝构造函数
    11. {
    12. _year = d._year;
    13. _month = d._month;
    14. _day = d._day;
    15. }
    16. private:
    17. int _year;
    18. int _month;
    19. int _day;
    20. };
    21. int main()
    22. {
    23. Date d1;
    24. Date d2(d1);
    25. return 0;
    26. }

    • 3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
           注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
    义类型是调用其拷贝构造函数完成拷贝的。

    1. class Time
    2. {
    3. public:
    4. Time()
    5. {
    6. _hour = 1;
    7. _minute = 1;
    8. _second = 1;
    9. }
    10. Time(const Time& t)
    11. {
    12. _hour = t._hour;
    13. _minute = t._minute;
    14. _second = t._second;
    15. cout << "Time::Time(const Time&)" << endl;
    16. }
    17. private:
    18. int _hour;
    19. int _minute;
    20. int _second;
    21. };
    22. class Date
    23. {
    24. private:
    25. // 基本类型(内置类型)
    26. int _year = 2023;
    27. int _month = 1;
    28. int _day = 1;
    29. // 自定义类型
    30. Time _t;
    31. };
    32. int main()
    33. {
    34. Date d1;
    35.    
    36.    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    37.    //Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构
    38. 造函数
    39. Date d2(d1);
    40. return 0;
    41. }
    4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了 ,还需要自己显式实现吗?
    当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

    1. // 这里会发现下面的程序会崩溃掉。
    2. typedef int DataType;
    3. class Stack
    4. {
    5. public:
    6. Stack(size_t capacity = 10)
    7. {
    8. _array = (DataType*)malloc(capacity * sizeof(DataType));
    9. if (nullptr == _array)
    10. {
    11. perror("malloc申请空间失败");
    12. return;
    13. }
    14. _size = 0;
    15. _capacity = capacity;
    16. }
    17. void Push(const DataType& data)
    18. {
    19. // ...
    20. _array[_size] = data;
    21. _size++;
    22. }
    23. ~Stack()
    24. {
    25. free(_array);
    26. }
    27. private:
    28. DataType *_array;
    29. size_t _size;
    30. size_t _capacity;
    31. };
    32. int main()
    33. {
    34. Stack s1;
    35. s1.Push(1);
    36. s1.Push(2);
    37. s1.Push(3);
    38. s1.Push(4);
    39. Stack s2(s1);
    40. return 0;
    41. }

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


    5. 赋值运算符重载

           C++ 为了增强代码的可读性引入了运算符重载 运算符重载是具有特殊函数名的函数 ,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
    •      简单来说就是:内置类型可以直接使用运算符运算,因为编译器知道如何运算,而自定义类型无法直接使用运算符,因为编译器不知道如何运算,所以如果要支持自定义类型使用运算符,需要自己实现运算符重载
    • 函数名字为:关键字operator后面接需要重载的运算符符号
    • 函数原型:返回值类型 operator操作符(参数列表)
    注意:
    • 不能通过连接其他符号来创建新的操作符:比如operator@
    • 重载操作符必须有一个类类型参数
    • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
    • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
    • .*   ::   sizeof   ?:    . 注意以上5个运算符不能重载。

    以日期类对象为例:

    1. class Date
    2. {
    3. public:
    4. Date(int year = 2023, int month = 1, int day = 1)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. //私有
    11.    // bool operator==(Date* this, const Date& d2)
    12.    // 这里需要注意的是,左操作数是this,指向调用函数的对象
    13. bool operator==(const Date& x)
    14. {
    15. return _year == x._year
    16. && _month == x._month
    17. && _day == x._day;
    18. }
    19. //private:
    20. int _year;
    21. int _month;
    22. int _day;
    23. };
    24. //公有
    25. bool operator==(Date x, Date y)
    26. {
    27. return x._year == y._year
    28. && x._month == y._month
    29. && x._day == y._day;
    30. }
    31. int main()
    32. {
    33. Date d1(2023, 9, 1);
    34. Date d2(2023, 9, 1);
    35. d1 == d2;
    36. cout << (d1 == d2) << endl;
    37. return 0;
    38. }

                -------------------------------------------------------------------------------------------------------------------

    依旧以日期类为例:

    1.复用逻辑

    2.请问下面代码为什么要有返回值?为什么要传引用返回?

    返回值是为了支持连续赋值,保持运算符的特性,类似于a = b = c 。d2 = d1 ;   d3 = d2 = d1 ;

    传引用返回是因为this指针出函数作用域后会销毁

    3.赋值重载与拷贝构造的区别

    4.日期类 +  与  +=

    5.关于日期类的前置++与后置++ 

    日期类案例声明:
    1. #pragma once
    2. #include <iostream>
    3. using namespace std;
    4. class Date
    5. {
    6. //友元
    7. friend ostream& operator<<(ostream& out, const Date& d); //流插入
    8. friend istream& operator>>(istream& in, Date& d);//流提取
    9. public:
    10. Date(int year = 2023, int month = 1, int day = 1);
    11. void Print();//打印
    12. int GetMonthDay(int year, int month) const;//获取每月的天数
    13. bool operator==(const Date& x) const;
    14. bool operator!=(const Date& x) const;
    15. bool operator<(const Date& x) const;
    16. bool operator<=(const Date& x) const;
    17. bool operator>(const Date& x) const;
    18. bool operator>=(const Date& x) const;
    19. Date& operator+=(int day);
    20. Date operator+(int day) const;
    21. //减不仅构成运算符重载,还构成函数重载
    22. //d1 - 100 日期 - 天数
    23. Date operator-(int day) const;
    24. Date& operator-=(int day);
    25. //d1 - d2 日期 - 日期
    26. int operator-(const Date& d);
    27. //++d1
    28. Date& operator++();
    29. //d1++
    30. Date operator++(int);
    31. //--d1 -> d1.operator--();
    32. Date& operator--();
    33. //d1-- -> d1.operator--(int);
    34. Date operator--(int);
    35. //void operator<<(ostream& out);
    36. private:
    37. int _year;
    38. int _month;
    39. int _day;
    40. };
    41. ostream& operator<<(ostream& out, const Date& d);//流插入
    42. istream& operator>>(istream& in, Date& d);//流提取

    日期类案例实现:
    1. #include"Date.h"
    2. int Date::GetMonthDay(int year, int month) const
    3. {
    4. static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };
    5. if (month == 2 && (((year % 400 == 0) || (year % 4 == 0)) && (year % 100 != 0)))
    6. {
    7. return 29;
    8. }
    9. else
    10. {
    11. return days[month];
    12. }
    13. }
    14. Date::Date(int year , int month , int day )
    15. {
    16. if (month > 0 && month < 13 &&
    17. (day > 0 && day <= GetMonthDay(year, month)))
    18. {
    19. _year = year;
    20. _month = month;
    21. _day = day;
    22. }
    23. else
    24. {
    25. cout << "日期非法" << endl;
    26. }
    27. }
    28. void Date::Print()
    29. {
    30. cout << _year << "/" << _month << "/" << _day << endl;
    31. }
    32. bool Date::operator==(const Date& x) const
    33. {
    34. return _year == x._year
    35. && _month == x._month
    36. && _day == x._day;
    37. }
    38. bool Date::operator!=(const Date& x) const
    39. {
    40. return !(*this == x);
    41. }
    42. bool Date::operator<(const Date& x) const
    43. {
    44. if (_year < x._year)
    45. {
    46. return true;
    47. }
    48. else if (_year == x._year && _month < x._month)
    49. {
    50. return true;
    51. }
    52. else if (_year == x._year && _month == x._month && _day < x._day)
    53. {
    54. return true;
    55. }
    56. else
    57. {
    58. return false;
    59. }
    60. }
    61. bool Date::operator<=(const Date& x) const
    62. {
    63. return *this < x || *this == x;
    64. }
    65. bool Date::operator>(const Date& x) const
    66. {
    67. return !(*this <= x);
    68. }
    69. bool Date::operator>=(const Date& x) const
    70. {
    71. return !(*this < x);
    72. }
    73. Date& Date:: operator+=(int day)
    74. {
    75. if (day < 0) //day为负数
    76. {
    77. *this -= -day;
    78. return *this;
    79. }
    80. _day += day;
    81. while (_day > GetMonthDay(_year, _month))
    82. {
    83. //减去多余的天数
    84. _day -= GetMonthDay(_year, _month);
    85. //月进位
    86. _month++;
    87. //年进位
    88. if (_month==13)
    89. {
    90. _month = 1;
    91. _year++;
    92. }
    93. }
    94. return *this;
    95. }
    96. //+ 复用+=
    97. Date Date :: operator+(int day) const
    98. {
    99. Date tmp(*this);
    100. tmp += day;
    101. return tmp;
    102. }
    103. //Date Date :: operator+(int day)
    104. //{
    105. // Date tmp(*this);
    106. //
    107. // tmp._day += day;
    108. //
    109. // while (tmp._day > GetMonthDay(tmp._year, tmp._month))
    110. // {
    111. // //减去多余的天数
    112. // tmp._day -= GetMonthDay(tmp._year, tmp._month);
    113. //
    114. // //月进位
    115. // tmp._month++;
    116. //
    117. // //年进位
    118. // if (tmp._month == 13)
    119. // {
    120. // tmp._month = 1;
    121. // tmp._year++;
    122. // }
    123. // }
    124. //
    125. // return tmp;
    126. //}
    127. //+= 复用+
    128. //Date& Date::operator+=(int day)
    129. //{
    130. // *this = *this + day;
    131. //
    132. // return *this;
    133. //}
    134. //++d1
    135. Date& Date::operator++()
    136. {
    137. *this += 1;
    138. return *this;
    139. }
    140. //d1++
    141. Date Date::operator++(int)
    142. {
    143. Date tmp(*this);
    144. *this += 1;
    145. return tmp;
    146. }
    147. Date& Date::operator-=(int day)
    148. {
    149. if (day < 0) //day为负数
    150. {
    151. *this += -day;
    152. return *this;
    153. }
    154. _day -= day;
    155. while (_day <= 0)
    156. {
    157. --_month;
    158. if (_month == 0)
    159. {
    160. --_year;
    161. _month = 12;
    162. }
    163. _day += GetMonthDay(_year, _month);
    164. }
    165. return *this;
    166. }
    167. Date Date::operator-(int day) const
    168. {
    169. Date tmp(*this);
    170. tmp -= day;
    171. return tmp;
    172. }
    173. //--d1
    174. Date& Date::operator--()
    175. {
    176. *this -= 1;
    177. return *this;
    178. }
    179. //d1--
    180. Date Date::operator--(int)
    181. {
    182. Date tmp(*this);
    183. *this -= 1;
    184. return tmp;
    185. }
    186. //d1 - d2
    187. int Date::operator-(const Date& d)
    188. {
    189. Date max = *this;
    190. Date min = d;
    191. int flag = 1;
    192. if (*this < d)
    193. {
    194. max = d;
    195. min = *this;
    196. flag = -1;
    197. }
    198. int count = 0;
    199. while (min != max)
    200. {
    201. ++min;
    202. ++count;
    203. }
    204. return count * flag;
    205. }
    206. //void Date::operator<<(ostream& out)
    207. //{
    208. // cout << _year << "年" << _month << "月" << _day << "日" << endl;
    209. //}
    210. ostream& operator<<(ostream& out, const Date& d)
    211. {
    212. cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    213. return out;
    214. }
    215. istream& operator>>(istream& in, Date& d)
    216. {
    217. in >> d._year >> d._month >> d._day;
    218. return in;
    219. }


    6. const 成员函数
            将 const 修饰的 成员函数 称之为 const 成员函数 const 修饰类成员函数,实际修饰该成员函数 隐含的 this 指针 ,表明在该成员函数中 不能对类的任何成员进行修改。

    7. 取地址及 const 取地址操作符重载
    这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
    1. class A
    2. {
    3. public:
    4. //const 修饰 *this
    5. //this指针的类型变成 const A*
    6. //内部不改变成员变量的成员函数,最好加上const,const对象和普通对象(权限的缩小)都可以调用
    7. void Print() const
    8. {
    9. cout << _a << endl;
    10. }
    11. A* operator&()
    12. {
    13. return this;
    14. }
    15. const A* operator&() const
    16. {
    17. return this;
    18. }
    19. private:
    20. int _a = 10;
    21. };
    22. void Func(const A& x)
    23. {
    24. x.Print();
    25. cout << &x << endl;
    26. }
    27. int main()
    28. {
    29. A aa;
    30. aa.Print();
    31. Func(aa);
    32. cout << &aa << endl;
    33. return 0;
    34. }
             这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!


    概念性内容均来自比特科技

  • 相关阅读:
    一个四位数,恰好等于去掉它的首位数字之后所剩的三位数的3倍,这个四位数是多少?
    Web(二)html5基础-超链接的应用(知识训练和编程训练)
    ovs vxlan 时延和吞吐
    如何在银行系统中做批量测试~
    golang 多个struct 转换融合为一个json,平级融合或者多级融合
    NODEJS杂记
    biopython----bio.PDB
    劫持TLS绕过canary pwn89
    Java8方法引用和Lambda表达式实例源码+笔记分享
    centos 7 中没有iptables 和service iptables save 指令使用失败问题解决方案
  • 原文地址:https://blog.csdn.net/m0_73969113/article/details/132630983