• 探索C++赋值运算符重载的内部机制:手把手教你精通



    W...Y的主页 😊

    代码仓库分享💕 


    🍔前言:

    前一篇博客中我们已经了解并学习了初始化和清理模块中的构造函数与析构函数,还有拷贝复制中的拷贝复制函数,它们都是类与对象中重要的成员,今天我们要来讲一下拷贝复制模块中另一个非常重要的内容——赋值重载,但是在学习赋值重载的同时我们应该先学习运算符重载,话不多说我们直接开始!

    目录

    赋值运算符重载

    运算符重载

     赋值运算符重载


    赋值运算符重载

    运算符重载

    C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
    返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. class Date
    5. {
    6. public:
    7. Date(int year = 1900, int month = 1, int day = 1)
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }
    13. // Date(const Date& d)  // 正确写法
    14. void print()
    15. {
    16. cout << _year << '/' << _month << '/' << _day << endl;
    17. }
    18. Date(const Date& d)  // 错误写法:编译报错,会引发无穷递归
    19. {
    20. _year = d._year;
    21. _month = d._month;
    22. _day = d._day;
    23. }
    24. private:
    25. int _year;
    26. int _month;
    27. int _day;
    28. };
    29. int main()
    30. {
    31. Date d1;
    32. Date d2(2022,10,23);
    33. int x = 1;
    34. int y = 2;
    35. bool ret = x > y;
    36. return 0;
    37. }

     上图是比较的反汇编代码,我们可以看出编译器可以直接做出运算符判断。

    一般情况下,我们去比较内置类型都非常容易去比较,因为内置类型都是一些基本变量int、char……我们可以直接使用运算符 ==、<、>等等去比较。但是自定义类型我们应该如何处理呢?

    一般情况下我们可以自己创建一个比较函数进行比较:

    1. //判断前面日期是否比后面大
    2. //大返回true,小返回false
    3. bool Greater(Date x, Date y)
    4. {
    5. if (x._year > y._year)
    6. {
    7. return true;
    8. }
    9. else if (x._year == y._year && x._month > y._month)
    10. {
    11. return true;
    12. }
    13. else if (x._year == y._year && x._month == y._month && x._day > y._day)
    14. {
    15. return true;
    16. }
    17. return false;
    18. }

    将此函数放入到类中才可以进行私有数据访问,要不就将基本数据公有化!

    上述函数可以轻松解决比大小的问题,但是还是有许多缺陷。比如:函数名的不规范就会导致我们不理解函数的作用。所以对于内置类型的比较C++针对起名字做出了一下规定:

    函数名字为:关键字operator后面接需要重载的运算符符号。
    函数原型:返回值类型 operator操作符(参数列表)

    注意:
    不能通过连接其他符号来创建新的操作符:比如operator@
    重载操作符必须有一个类类型参数
    用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
    作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
    藏的this

    .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。 

    那针对以上函数我们就可以这样修改:

    1. bool operator>(const Date& x, const Date& y)
    2. {
    3. if (x._year > y._year)
    4. {
    5. return true;
    6. }
    7. else if (x._year == y._year && x._month > y._month)
    8. {
    9. return true;
    10. }
    11. else if (x._year == y._year && x._month == y._month && x._day > y._day)
    12. {
    13. return true;
    14. }
    15. return false;
    16. }

    在参数选择时,我们选择引用而不选择直接传参,这样可以节约时间效率,不用调用拷贝构造函数。 

     这样做我们用户一眼就可以看出此函数的作用,是用来比大小的函数。但是这样去调用函数时函数名还是太长了,而且不直白不美观,所以我们在使用时可以使用非常简单的方式进行调用:

    bool ret1 = d1 > d2;

    只需要一个运算符就可以调用此函数,是不是非常方便呢!编译器可以帮助我们将这句话转变为,调用函数的语句。

    注意:运算符重载与函数重载中都有重载二字,但是绝对是没有任何关系的,函数重载是可以允许参数不同的同名函数,运算符重载是自定义类型可以直接使用运算符。不是姓“张”的都有血缘关系。

    但是当我们万事俱备之后进行编译却还是出现报错: 这是为什么呢?

    为什么会多一个参数呢?哪里多了?成员参数都会有一个隐藏的参数this指针。所以参数不匹配导致程序出问题。相当于我们在调用函数时使用了两个参数,而设定的参数中却有三个!

    我们应该怎么解决问题呢?删一个即可。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. class Date
    5. {
    6. public:
    7. Date(int year = 1900, int month = 1, int day = 1)
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }
    13. // Date(const Date& d)  // 正确写法
    14. void print()
    15. {
    16. cout << _year << '/' << _month << '/' << _day << endl;
    17. }
    18. //Date(const Date& d)  // 错误写法:编译报错,会引发无穷递归
    19. //{
    20. // _year = d._year;
    21. // _month = d._month;
    22. // _day = d._day;
    23. //}
    24. bool operator>(const Date& y)
    25. {
    26. if (_year > y._year)
    27. {
    28. return true;
    29. }
    30. else if (_year == y._year && _month > y._month)
    31. {
    32. return true;
    33. }
    34. else if (_year == y._year && _month == y._month && _day > y._day)
    35. {
    36. return true;
    37. }
    38. return false;
    39. }
    40. private:
    41. int _year;
    42. int _month;
    43. int _day;
    44. };
    45. //判断日期是否相等
    46. //bool Greater(Date x, Date y)
    47. //bool Compare1(Date x, Date y)
    48. int main()
    49. {
    50. Date d1;
    51. Date d2(2022,10,23);
    52. /*d1 == d2;
    53. d1 > d2;*/
    54. bool ret1 = d1 > d2;
    55. int x = 1, y = 2;
    56. bool ret = x > y;
    57. return 0;
    58. }

     我们将参数删除一个即可,编译器看就会转换成d1.operator(d2)在进行转换成d1.operator(&d1,d2)即可,地址就是this指针指向的内容。

    我们学明白了运算符重载,那么在进行赋值运算符重载就非常easy了。

     赋值运算符重载

    赋值运算符重载格式
    参数类型:const T&,传递引用可以提高传参效率
    返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
    检测是否自己给自己赋值
    返回*this :要复合连续赋值的含义

    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. Date& operator=(const Date& d)
    17. {
    18. if(this != &d)
    19.    {
    20.       _year = d._year;
    21.       _month = d._month;
    22.       _day = d._day;
    23.    }
    24.    
    25.     return *this;
    26. }
    27. private:
    28. int _year ;
    29. int _month ;
    30. int _day ;
    31. };

    赋值运算符只能重载成类的成员函数不能重载成全局函数

    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. int _year;
    11. int _month;
    12. int _day;
    13. };
    14. // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
    15. Date& operator=(Date& left, const Date& right)
    16. {
    17. if (&left != &right)
    18. {
    19. left._year = right._year;
    20. left._month = right._month;
    21. left._day = right._day;
    22. }
    23. return left;
    24. }
    25. // 编译失败:
    26. // error C2801: “operator =”必须是非静态成员

    原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
    一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
    运算符重载只能是类的成员函数。 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
    意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
    重载完成赋值

    1. class Time
    2. {
    3. public:
    4. Time()
    5. {
    6. _hour = 1;
    7. _minute = 1;
    8. _second = 1;
    9. }
    10. Time& operator=(const Time& t)
    11. {
    12. if (this != &t)
    13. {
    14. _hour = t._hour;
    15. _minute = t._minute;
    16. _second = t._second;
    17. }
    18. return *this;
    19. }
    20. private:
    21. int _hour;
    22. int _minute;
    23. int _second;
    24. };
    25. class Date
    26. {
    27. private:
    28. // 基本类型(内置类型)
    29. int _year = 1970;
    30. int _month = 1;
    31. int _day = 1;
    32. // 自定义类型
    33. Time _t;
    34. };
    35. int main()
    36. {
    37. Date d1;
    38. Date d2;
    39. d1 = d2;
    40. return 0;
    41. }

    既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
    现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

    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. // CheckCapacity();
    20. _array[_size] = data;
    21. _size++;
    22. }
    23. ~Stack()
    24. {
    25. if (_array)
    26. {
    27. free(_array);
    28. _array = nullptr;
    29. _capacity = 0;
    30. _size = 0;
    31. }
    32. }
    33. private:
    34. DataType *_array;
    35. size_t _size;
    36. size_t _capacity;
    37. };
    38. int main()
    39. {
    40. Stack s1;
    41. s1.Push(1);
    42. s1.Push(2);
    43. s1.Push(3);
    44. s1.Push(4);
    45. Stack s2;
    46. s2 = s1;
    47. return 0;
    48. }

     注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
    须要实现。


    以上就是赋值运算符重载全部内容!!!感谢大家观看。 

  • 相关阅读:
    CTF-Misc——图片分析
    实现一个简单的mybatis:SimpleMyBatis
    netty 底层的工作原理
    记一次重大的问题解决
    《工程伦理与学术道德》第二章习题
    视频号双11激励政策,快来看一看
    【rust】| 06——语言特性 | 所有权
    如何增加shopee店铺搜索流量—成都扬帆牧哲教育咨询
    spring-boot入门之如何利用idea创建一个spring-boot项目
    解决外接显示器后Edge浏览器地址栏等变得很大的问题
  • 原文地址:https://blog.csdn.net/m0_74755811/article/details/133997034