事实上,我们的重载运算符返回void、返回对象本身、返回对象引用都是可以的,并不是说一定要返回一个引用,只不过在不同的情况下需要不同的返回值。

那么什么情况下要返回对象的引用呢?

原因有两个:

  •   允许进行连续赋值
  • 防止返回对象(返回对象也可以进行连续赋值(常规的情况,如a = b = c,而不是(a = b) = c))的时候调用拷贝构造函数和析构函数导致不必要的开销,降低赋值运算符的效率。

  

  对于第二点原因:如果用”值传递“的方式,虽然功能仍然正确,但由于return语句要把*this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,会降低赋值函数的效率。

  场景:

  需要返回对象引用或者返回对象(效率没有返回引用高),需要实现连续赋值,使重载的运算符更符合C++本身的运算符语意,如连续赋值 = += -= *= 、=,<<输出流

  关于赋值 =,我们知道赋值=有连续等于的特性

  1. int x,y,z;
  2. x=y=z=;

  同样有趣的是,赋值采用的是右结合律,所以上述连锁赋值被解析为:

  1. x=(y=(z=));//赋值连锁形式

  这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x。

  为了实现”连锁赋值“,赋值操作符号返回一个reference(引用)指向操作符号的左侧实参(而事实上重载运算符的左侧实参就是调用对象本身,比如= += -=等),这是你为classes实现赋值操作符时应该遵循的协议:这个协议不仅仅适用于以上的标准赋值形式,也适用于所有赋值运算。

  1. class Widght{
  2. public:
  3. .....
  4. Widget& operator=(cosnt Widget& rhs)
  5. {
  6. ...
  7. return* this;
  8. }
  9. Widget& operator+=(cosnt Widget& rhs)
  10. {
  11. ...
  12. return* this;
  13. }
  14.  
  15. Widget& operator-=(cosnt Widget& rhs)
  16. {
  17. ...
  18. return* this;
  19. }
  20.  
  21. Widget& operator*=(cosnt Widget& rhs)
  22. {
  23. ...
  24. return* this;
  25. }
  26.  
  27. Widget& operator/=(cosnt Widget& rhs)
  28. {
  29. ...
  30. return* this;
  31. }
  32. ...
  33. };

  注意,这只是个协议,并无强制性,如果不遵循它,代码一样可以通过编译,然而这份协议被所有内置类型和标准程序库提供的类型入string,vector,complex,std::trl::shared_ptr或者即将提供的类型共同遵守。因此除非你有一个标新立异的好理由,不然还是随众吧。

  下面看一个赋值运算符重载的例子:(连续赋值,常规的情况(a = b = c)

  1、首先是返回对象的情况:

  1. #include <iostream>
  2. using namespace std;
  3. class String
  4. {
  5. private:
  6. char *str;
  7. int len;
  8. public:
  9. String(const char* s);//构造函数声明
  10. String operator=(const String& another);//运算符重载,此时返回的是对象
  11. void show()
  12. {
  13. cout << "value = " << str << endl;
  14. }
  15.  
  16. /*copy construct*/
  17. String(const String& other)
  18. {
  19. len = other.len;
  20. str = new char[len + ];
  21. strcpy(str, other.str);
  22. cout << "copy construct" << endl;
  23. }
  24.  
  25. ~String()
  26. {
  27. delete[] str;
  28. cout << "deconstruct" << endl;
  29. }
  30. };
  31.  
  32. String::String(const char* s)//构造函数定义
  33. {
  34. len = strlen(s);
  35. str = new char[len + ];
  36. strcpy(str, s);
  37. }
  38.  
  39. String String::operator=(const String &other)//运算符重载
  40. {
  41. if (this == &other)
  42. return *this;
  43. // return;
  44. delete[] str;
  45. len = other.len;
  46. str = new char[len + ];
  47. strcpy(str, other.str);
  48. return *this;
  49. // return;
  50. }
  51.  
  52. int main()
  53. {
  54. String str1("abc");
  55. String str2("");
  56. String str3("");
  57. str1.show();
  58. str2.show();
  59. str3.show();
  60. str3 = str1 = str2;//str3.operator=(str1.operator=(str2))
  61. str3.show();
  62. str1.show();
  63. return ;
  64. }

  运行结果:

  

  2、下面是返回引用的情况(String& operator = (const String& str)),直接贴运行结果:

  

  

  当运算符重载返回的是对象时,会在连续赋值运算过程的返回途中,调用两次拷贝构造函数和析构函数(因为return的是个新的对象)

  如果采用String& operator = (const String& str)这样就不会有多余的调用(因为这里直接return一个已经存在的对象的引用)

  上面的栗子也说明一点:析构函数的调用是在变量作用域结束的时候(以及程序运行结束的时候)

  如果采用return对象,那么第二次赋值运算调用的情况就是

  将一个新的String对象(returnStringObj)传递到operator = (const String& str)的参数中去 相当于

  1. const String&str = returnStringObj;

  如果采用return对象引用,那么第二次赋值运算的情况就是

  将一个已经存在的String对象的引用((其实就是str1))传递给operator = (const String& str)的参数中去

  1. const String&str = returnReference; //(String& returnReference = str1;)

  +=等运算符也是同样的考虑,比如

  1. int main()
  2. {
  3. String str1("abc");
  4. String str2("");
  5. String str3("");
  6. str1.show();
  7. str2.show();
  8. str3.show();
  9. str3 = str1 = str2;//str3.operator=(str1.operator=(str2))
  10. str3.show();
  11. str1.show();
  12.  
  13. int num = ;
  14. num += (num += );
  15. cout << num << endl;
  16. return ;
  17. }

  

  如果使用+=或其它上面举出的运算符进行连续操作时,,则这些运算符的返回值一定要是一个对象或者引用才行,不然就会出现错误(参数类型不符合)。什么意思呢,下面举个栗子。

  我现在让运算符重载返回的类型为空,单个赋值,不使用连续赋值

  1. #include <iostream>
  2. using namespace std;
  3. class String
  4. {
  5. private:
  6. char *str;
  7. int len;
  8. public:
  9. String(const char* s);//构造函数声明
  10. void operator=(const String& another);//运算符重载,此时返回为空
  11. void show()
  12. {
  13. cout << "value = " << str << endl;
  14. }
  15.  
  16. /*copy construct*/
  17. String(const String& other)
  18. {
  19. len = other.len;
  20. str = new char[len + ];
  21. strcpy(str, other.str);
  22. cout << "copy construct" << endl;
  23. }
  24.  
  25. ~String()
  26. {
  27. delete[] str;
  28. cout << "deconstruct" << endl;
  29. }
  30. };
  31.  
  32. String::String(const char* s)
  33. {
  34. len = strlen(s);
  35. str = new char[len + ];
  36. strcpy(str, s);
  37. }
  38.  
  39. void String::operator=(const String &other)
  40. {
  41. if (this == &other)
  42. // return *this;
  43. return;
  44. delete[] str;
  45. len = other.len;
  46. str = new char[len + ];
  47. strcpy(str, other.str);
  48. // return *this;
  49. return;
  50. }
  51.  
  52. int main()
  53. {
  54. String str1("abc");
  55. String str2("");
  56. String str3("");
  57. str1.show();
  58. str2.show();
  59. str3.show();
  60. str3 = str1;//这样OK
  61. str3.show();
  62. str1.show();
  63. return ;
  64. }

  运行结果:

  

  但当我把主函数中str1,str2,str3改为连续赋值时:

  1. int main()
  2. {
  3. String str1("abc");
  4. String str2("");
  5. String str3("");
  6. str1.show();
  7. str2.show();
  8. str3.show();
  9. str3 = str1=str2;//这样不OK
  10. str3.show();
  11. str1.show();
  12. return ;
  13. }

  出错:

  

  所以,当你确定不使用连续赋值时,直接返回void也是可以的。要明白一点:

  运算符左侧的对象就是操作对象,比如

  1. ObjectA = ObjectB 等同于ObjectA.operator=(ObjectB
  2. ObjectA+=ObjectB 等同于ObjectA.operator+(ObjectB

  

  最后要说明一点:并非必须返回引用,返回引用的好处既可以避免拷贝构造函数和析构函数的调用,又可以保证= +=等运算符的原始语义清晰。

  啥叫原始语义清晰呢?

  如

  1. (str3 = str1) = str2;

  我们的意识里,就是先执行括号内容,即str1赋值给str3,然后str2再赋值给str3,最后str3输出的内容是str2的。

  即如果运算符重载返回的是对象引用时,

  1. //返回的是对象引用的情况
  2. #include <iostream>
  3. using namespace std;
  4. class String
  5. {
  6. private:
  7. char *str;
  8. int len;
  9. public:
  10. String(const char* s);//构造函数声明
  11. String& operator=(const String& another);//运算符重载,此时返回为引用
  12. void show()
  13. {
  14. cout << "value = " << str << endl;
  15. }
  16.  
  17. /*copy construct*/
  18. String(const String& other)
  19. {
  20. len = other.len;
  21. str = new char[len + ];
  22. strcpy(str, other.str);
  23. cout << "copy construct" << endl;
  24. }
  25.  
  26. ~String()
  27. {
  28. delete[] str;
  29. cout << "deconstruct" << endl;
  30. }
  31. };
  32.  
  33. String::String(const char* s)
  34. {
  35. len = strlen(s);
  36. str = new char[len + ];
  37. strcpy(str, s);
  38. }
  39.  
  40. String& String::operator=(const String &other)
  41. {
  42. if (this == &other)
  43. return *this;
  44. // return;
  45. delete[] str;
  46. len = other.len;
  47. str = new char[len + ];
  48. strcpy(str, other.str);
  49. return *this;
  50. // return;
  51. }
  52.  
  53. int main()
  54. {
  55. String str1("abc");
  56. String str2("");
  57. String str3("");
  58. str1.show();
  59. str2.show();
  60. str3.show();
  61. (str3 = str1) = str2;
  62. cout << "str3的内容为:" << endl;
  63. str3.show();
  64. return ;
  65. }

  运行结果:

  

  str3得到了str2的内容,与我们认识的‘=’运算符逻辑相符。

  而如果运算符重载返回的是对象时,

  1. //这是返回类型为对象的情况
  2. #include <iostream>
  3. using namespace std;
  4. class String
  5. {
  6. private:
  7. char *str;
  8. int len;
  9. public:
  10. String(const char* s);//构造函数声明
  11. String operator=(const String& another);//运算符重载,此时返回为空
  12. void show()
  13. {
  14. cout << "value = " << str << endl;
  15. }
  16.  
  17. /*copy construct*/
  18. String(const String& other)
  19. {
  20. len = other.len;
  21. str = new char[len + ];
  22. strcpy(str, other.str);
  23. cout << "copy construct" << endl;
  24. }
  25.  
  26. ~String()
  27. {
  28. delete[] str;
  29. cout << "deconstruct" << endl;
  30. }
  31. };
  32.  
  33. String::String(const char* s)
  34. {
  35. len = strlen(s);
  36. str = new char[len + ];
  37. strcpy(str, s);
  38. }
  39.  
  40. String String::operator=(const String &other)
  41. {
  42. if (this == &other)
  43. return *this;
  44. // return;
  45. delete[] str;
  46. len = other.len;
  47. str = new char[len + ];
  48. strcpy(str, other.str);
  49. return *this;
  50. // return;
  51. }
  52.  
  53. int main()
  54. {
  55. String str1("abc");
  56. String str2("");
  57. String str3("");
  58. str1.show();
  59. str2.show();
  60. str3.show();
  61. (str3 = str1) = str2;
  62. cout << "赋值后str3的内容为:" << endl;
  63. str3.show();
  64. return ;
  65. }

  运行结果:

  

  str3只得到了str1的内容,并没有得到str2的内容,这是因为执行(str3=str1)后,因为返回的是对象(一个临时对象,str3的一个拷贝),不是引用,所以此时str3不在后面的‘=str2’的操作中,而是str2对一个临时对象赋值,所以str3的内容保持不变(等于str1)。

  总结

  所以,对此类运算符重载时,还是老老实实的返回引用,少搞事,做个好男孩:)

  

c++中有些重载运算符为什么要返回引用的更多相关文章

  1. C++ 重载运算符和重载函数

    C++ 重载运算符和重载函数 C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是 ...

  2. C++解析七-重载运算符和重载函数

    重载运算符和重载函数C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载.重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列 ...

  3. 吴裕雄--天生自然C++语言学习笔记:C++ 重载运算符和重载函数

    C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不 ...

  4. c++的重载运算符

    c++中允许重载运算符: 这是我辛苦的结果 #include"iostream"using namespace std;class aaa{ int x;public: aaa() ...

  5. JavaScript中的逗号运算符

    JavaScript逗号运算符  阅读本文的前提,明确表达式.短语.运算符.运算数这几个概念. 所谓表达式,就是一个JavaScript的“短语”,JavaScript解释器可以计算它,从而生成一个值 ...

  6. C++ 重载运算符简单举例

    我们可以重定义或重载大部分 C++ 内置的运算符.这样,就能使用自定义类型的运算符. 重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的.与其他函数一 ...

  7. C++中,用类和重载运算符写高精模板

    先放代码: #include<iostream> #include<cstdio> #include<cstring> using namespace std; s ...

  8. 高精度运算略解 在struct中重载运算符

    高精度 高精度,即高精度算法,属于处理大数字的数学计算方法.在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字. 重载运算符 运算符重载,就是对已有的运算符重新进行 ...

  9. 【C++】C++中重载运算符和类型转换

    输入输出运算符 输入输出运算符 输入输出运算符 算术和关系运算符 相等运算符 关系运算符 赋值运算符 复合赋值运算符 下标运算符 递增和递减运算符 成员访问运算符 函数调用运算符 lambda是函数对 ...

随机推荐

  1. "A transport-level error has occurred when sending the request to the server,指定的网络名不在可用"的解决办法

    项目在外网服务器上运行的时候,遇到一个异常:"A transport-level error has occurred when sending the request to the ser ...

  2. appid 评价

    //apple api #define kAppAppleId         @"980883989" #define kAppRateUrl         @"it ...

  3. hash-5.ConcurrentHashMap

    http://www.cnblogs.com/dolphin0520/p/3932905.html有时间细看

  4. C#调用java类、jar包方法

    一.将已经编译后的java中Class文件进行打包:打包命令JAR 如:将某目录下的所有class文件夹全部进行打包处理: 使用的命令:jar cvf test.jar -C com/ . 其中tes ...

  5. mongodb python image 图像存储读取

    最近做一些数据库调研的工作,目标是实现影像更快的入库.出库.查询,并实现并行访问等操作. 将结果总结成一个mongoImg类,也算是小结吧. ''' Created on 2013-8-6 class ...

  6. ubuntu安装android开发环境

    1.安装oracle-jdk 打开终端,使用下面的命令: java -version 如果你看到像下面的输出,这就意味着你并没有安装过Java: The program ‘java’ can be f ...

  7. sql server2008 字符串的替换

    DECLARE @TSql VARCHAR(MAX) SET @TSql =REPLACE(@TSql,'#PrimaryKey','0'); 1,@TSql将要替换的完整字符串 2,#Primary ...

  8. matplotlib绘制直方图【柱状图】

    代码: def drawBar(): xticks = ['A', 'B', 'C', 'D', 'E']#每个柱的下标说明 gradeGroup = {'A':200,'B':250,'C':330 ...

  9. JAVA8 十大新特性详解

    前言: Java8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.在Java Code Geeks上已经有很多介绍Java 8新特性的文章, 例如Playing with Java ...

  10. 对称加密之AES、压缩解压以及压缩加密解密解压综合实战

    AES 压缩解压 压缩加密解密解压 对称加密: 就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密.密钥是控制加密及解密过程的指令.算法是一组规则,规定如何进行加密和解密.   因此加密的安 ...