当前位置:   article > 正文

NzN的C++之路--拷贝构造函数&&赋值运算符重载

NzN的C++之路--拷贝构造函数&&赋值运算符重载

目录

Part 1 拷贝构造函数

一、概念

二、特征

Part 2 赋值运算符重载

一、运算符重载

二、赋值运算符重载

三、前置++和后置++重载

Part 3 const成员

Part 4 取地址及const取地址操作符重载 


Part 1 拷贝构造函数

一、概念

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

  1. class Date
  2. {
  3. public:
  4. Date(int year = 1, 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. private:
  15. int _year;
  16. int _month;
  17. int _day;
  18. };
  19. int main()
  20. {
  21. Date d1(2024, 4, 10);
  22. //拷贝构造:用同类型的对象初始化(下面两种写法都对)
  23. Date d2(d1);
  24. Date d3 = d1;
  25. }

二、特征

  • 拷贝构造函数是构造函数的一个重载形式。
  1. //全缺省的默认构造
  2. Date(int year = 1, int month = 1, int day = 1)
  3. {
  4. _year = year;
  5. _month = month;
  6. _day = day;
  7. }
  8. //拷贝构造
  9. Date(Date& d)
  10. {
  11. _year = d._year;
  12. _month = d._month;
  13. _day = d._day;
  14. }
  • 拷贝构造函数只有一个参数且必须是类类型对象的引用,使用传值方式会引发无穷递归调用,编译器会强制检查并直接报错。如果是传指针,不会构成无穷递归,但此时按规定,它只是普通的构造函数,不是拷贝构造函数。
  1. void Func(Date& d)
  2. {
  3. d.Print();
  4. }
  5. int main()
  6. {
  7. Date d1(2024, 4, 10);
  8. //内置类型会直接拷贝,自定义类型传值传参会调用拷贝构造
  9. Func(d1);//调用完拷贝构造,才会调用Func
  10. }
  1. void Func(Date& d)
  2. {
  3. d.Print();
  4. }
  5. int main()
  6. {
  7. Date d2(2024, 4, 11);
  8. //不想调用拷贝构造,我们就可以传指针/传引用
  9. //Func(&d2);
  10. Func(d2);
  11. }
  1. class Date
  2. {
  3. public:
  4. //全缺省的默认构造
  5. Date(int year = 1, int month = 1, int day = 1)
  6. {
  7. _year = year;
  8. _month = month;
  9. _day = day;
  10. }
  11. //拷贝构造
  12. Date(Date& d)
  13. {
  14. _year = d._year;
  15. _month = d._month;
  16. _day = d._day;
  17. }
  18. void Print()
  19. {
  20. cout << _year << "-" << _month << "-" << _day << endl;
  21. }
  22. private:
  23. int _year;
  24. int _month;
  25. int _day;
  26. };
  27. int main()
  28. {
  29. Date d1(2024, 4, 10);
  30. Date d2(d1);//如果这里是传值,就会调用拷贝构造Date d(d1)
  31. //调用的拷贝构造还要继续调用拷贝构造Date d(d1),就构成了无限递归
  32. d2.Print();
  33. }
  • 建议在拷贝构造的参数部分加上const。

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

  1. //栈的深拷贝
  2. Stack(const Stack& st)
  3. {
  4. //开辟和st1一样大的空间
  5. _array= (DataType*)malloc(sizeof(DataType) * st._capacity);
  6. if (NULL == _array)
  7. {
  8. perror("malloc fail");
  9. return;
  10. }
  11. //把st1数组里的数据拷贝到新开辟的这块空间
  12. memcpy(_array, st._array, sizeof(DataType) * st._size);
  13. //size和capacity都要跟st1一样大
  14. _size = st._size;
  15. _capacity = st._capacity;
  16. }

【总结】

       类中如果没有涉及资源申请时,拷贝构造可写可不写;一旦涉及到资源申请,则一定要写拷贝构造函数。一般情况下,不需要显式写析构函数,就不需要显式写拷贝构造。如果内部有一些指针或值指向资源,需要显式写析构函数,一般就要显式写深拷贝。

       那如果实例化了两个日期,那该如何比较它们的大小呢?

  1. class Date
  2. {
  3. public:
  4. Date(int year = 1, 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. //private://这里如果是private,在下面比较大小中就无法使用成员变量
  15. int _year;
  16. int _month;
  17. int _day;
  18. };
  19. //bool DateGreater(const Date& dt1, const Date& dt2)//引用传参,防止传参时调用拷贝构造
  20. bool operator>(const Date& dt1, const Date& dt2)//引用传参,防止传参时调用拷贝构造
  21. {
  22. if (dt1._year > dt2._year)
  23. {
  24. return true;
  25. }
  26. else if (dt1._year == dt2._year)
  27. {
  28. if (dt1._month > dt2._month)
  29. {
  30. return true;
  31. }
  32. else if (dt1._month == dt2._month)
  33. {
  34. return dt1._day > dt2._day;
  35. }
  36. }
  37. return false;
  38. }
  39. //bool DateLess(const Date& dt1, const Date& dt2)
  40. bool operator<(const Date& dt1, const Date& dt2)//引用传参,防止传参时调用拷贝构造
  41. {
  42. if (dt1._year < dt2._year)
  43. {
  44. return true;
  45. }
  46. else if (dt1._year == dt2._year)
  47. {
  48. if (dt1._month < dt2._month)
  49. {
  50. return true;
  51. }
  52. else if (dt1._month == dt2._month)
  53. {
  54. return dt1._day < dt2._day;
  55. }
  56. }
  57. return false;
  58. }
  59. int main()
  60. {
  61. Date d1(2024, 4, 10);
  62. Date d2(2024, 4, 11);
  63. //内置类型可以直接比较
  64. //自定义类型必须要自己构造比较函数
  65. /*int i = DateGreater(d1, d2);
  66. cout << i << endl;*/
  67. //operator+运算符就可以构成函数名,可以显式调用
  68. //一般不会显式调用,这样写是为了支持d1 > d2这种写法
  69. cout << operator<(d1, d2) << endl;
  70. cout << (d1 > d2) << endl;//构成了运算符重载,这样也可以比较自定义类型
  71. return 0;
  72. }

        这样我们就引入了运算符重载的内容。

Part 2 赋值运算符重载

一、运算符重载

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

函数名:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符,比如operator@
  • 重载操作符至少有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,但并没有强制要求,只是为了尽可能规范
  • .*    ::    sizeof    ? :    .  这5个运算符不能重载
  1. class OB
  2. {
  3. public:
  4. void func()
  5. {
  6. cout << "void func()" << endl;
  7. }
  8. };
  9. typedef void(OB::* PtrFunc)();//成员函数指针类型
  10. int main()
  11. {
  12. //成员函数要加&才能取到函数指针
  13. PtrFunc fp = &OB::func;//定义成员函数指针p指向函数func
  14. OB tmp;//定义OB类对象tmp
  15. (tmp.*fp)();//用tmp对象调用函数指针就需要.*
  16. return 0;
  17. }
  1. class Date
  2. {
  3. public:
  4. Date(int year = 2024, int month = 4, int day = 14)
  5. {
  6. _year = year;
  7. _month = month;
  8. _day = day;
  9. }
  10. //private:
  11. int _year;
  12. int _month;
  13. int _day;
  14. };
  15. bool operator==(const Date& d1, const Date& d2)
  16. {
  17. return d1._year == d2._year
  18. && d1._month == d2._month
  19. && d1._day == d2._day;
  20. }
  21. int main()
  22. {
  23. Date d1(2024, 4, 15);
  24. Date d2(2024, 4, 16);
  25. //operator==(d1, d2);//显式调用无法发挥其优势
  26. cout << (d1 == d2) << endl;//编译器会直接把d1 == d2转换成operator==(d1, d2)
  27. }

        运算符重载成全局的需要成员变量是公有的,为了保证封装性,有三种方式:

        1. 提供这些成员的get和set(Java较为常用)

        2. 后面学习的友元

        3. 重载为成员函数(C++常用)

  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  1. //operator == ”的参数太多,因为成员函数会有一个隐含的参数this
  2. bool operator==(const Date& d1, const Date& d2)
  3. {
  4. return d1._year == d2._year
  5. && d1._month == d2._month
  6. && d1._day == d2._day;
  7. }
  1. //形参看上去会比操作数数目少1
  2. bool operator==(const Date& d)
  3. {
  4. return this->_year == d._year
  5. && this->_month == d._month
  6. && this->_day == d._day;
  7. }

注意:如果运算符重载同时出现在类成员函数和全局中,优点调用类的成员函数。

二、赋值运算符重载

  1. int main()
  2. {
  3. Date d1(2024, 4, 15);
  4. //拷贝构造
  5. Date d2(d1);
  6. Date d3 = d1;
  7. Date d4(2024, 4, 16);
  8. d1 = d4;//这里就不是拷贝构造,而是赋值拷贝/赋值重载
  9. //拷贝构造是用一个对象去初始化另一个对象
  10. //赋值重载是复制一个对象,把值赋给另一个已经存在的对象
  11. }

赋值运算符重载格式:

  • 参数类型:const 类名&,传递引用可以提高传参效率
  • 返回值类型:返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回值:*this ,要复合连续赋值的含义
  1. //赋值重载
  2. //返回值为类类型的引用,便于连续赋值
  3. Date& operator=(const Date& d)
  4. {
  5. _year == d._year;
  6. _month == d._month;
  7. _day == d._day;
  8. //d2=d1,this是d2的地址,*this是d2,d1就是d
  9. return *this;
  10. }

 注意:如果是返回值,而不是引用,就会生成这个值的临时对象的拷贝。

总结:不能用引用返回一个临时对象或局部对象,因为该对象出了当前所在函数的作用域就会被销毁,引用对象在该函数栈帧已经销毁了。

        虽然引用返回可以减少一次调用拷贝构造,但是也要看情况。当出了函数作用域,引用对象还在,才可以返回引用。即返回对象没析构用引用返回,析构了就传值返回。

  • 没有显式实现时,编译器会默认生成一个赋值运算符重载,以值的方式逐字节拷贝(与拷贝构造一样)。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符。

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

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

【总结】

       跟拷贝构造类似,Date类等不涉及资源申请的可以使用默认生成的赋值重载;但是Stack类这种需要申请资源的需要自己实现赋值重载。

三、前置++和后置++重载

  1. //前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
  2. //C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数由编译器自动传递
  3. //前置++:返回+1之后的结果
  4. //this指向的对象函数结束后不会销毁,引用返回可以提高效率
  5. Date& operator++()
  6. {
  7. _day += 1;
  8. return *this;
  9. }
  10. //后置++:返回+1前的结果
  11. //注意:后置++是先用后加,因此需要在实现时需要先将this保存一份,然后this+1
  12. //tmp是临时对象,因此只能以值的方式返回,不能返回引用
  13. Date operator++(int)
  14. {
  15. Date tmp(*this);
  16. _day += 1;
  17. return tmp;
  18. }

注意:函数重载是函数名相同而参数列表不同;赋值运算符重载是指让自定义类型也可以控制运算符,增强代码可读性。多个同一运算符的重载可以构成函数重载。

Part 3 const成员

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

Part 4 取地址及const取地址操作符重载 

  1. class Date
  2. {
  3. public:
  4. //两个的区别就是返回值和参数类型的不同
  5. Date* operator&()
  6. {
  7. return this;
  8. }
  9. const Date* operator&()const
  10. {
  11. return this;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

        这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人取到真实的地址,一般不会出现这种状况。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/788967
推荐阅读
相关标签
  

闽ICP备14008679号