zoukankan      html  css  js  c++  java
  • c++中有些重载运算符为什么要返回引用

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

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

    原因有两个:

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

      

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

      场景:

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

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

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

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

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

      这里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 + 1];
    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 + 1];
    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 + 1];
    47     strcpy(str, other.str);
    48     return *this;
    49 //    return;
    50 }
    51 
    52 int main()
    53 {
    54     String str1("abc");
    55     String str2("123");
    56     String str3("456");
    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 0;
    64 }

      运行结果:

      

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

      

      

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

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

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

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

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

    const String&str = returnStringObj;

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

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

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

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

     1 int main()
     2 {
     3     String str1("abc");
     4     String str2("123");
     5     String str3("456");
     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 = 10;
    14     num += (num += 100);
    15     cout << num << endl;
    16     return 0;
    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 + 1];
    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 + 1];
    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 + 1];
    47     strcpy(str, other.str);
    48 //    return *this;
    49     return;
    50 }
    51 
    52 int main()
    53 {
    54     String str1("abc");
    55     String str2("123");
    56     String str3("456");
    57     str1.show();
    58     str2.show();
    59     str3.show();
    60     str3 = str1;//这样OK
    61     str3.show();
    62     str1.show();
    63     return 0;
    64 }

      运行结果:

       

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

     1 int main()
     2 {
     3     String str1("abc");
     4     String str2("123");
     5     String str3("456");
     6     str1.show();
     7     str2.show();
     8     str3.show();
     9     str3 = str1=str2;//这样不OK
    10     str3.show();
    11     str1.show();
    12     return 0;
    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 + 1];
    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 + 1];
    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 + 1];
    48     strcpy(str, other.str);
    49     return *this;
    50 //    return;
    51 }
    52 
    53 int main()
    54 {
    55     String str1("abc");
    56     String str2("123");
    57     String str3("456");
    58     str1.show();
    59     str2.show();
    60     str3.show();
    61     (str3 = str1) = str2;
    62     cout << "str3的内容为:" << endl;
    63     str3.show();
    64     return 0;
    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 + 1];
    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 + 1];
    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 + 1];
    48     strcpy(str, other.str);
    49     return *this;
    50 //    return;
    51 }
    52 
    53 int main()
    54 {
    55     String str1("abc");
    56     String str2("123");
    57     String str3("456");
    58     str1.show();
    59     str2.show();
    60     str3.show();
    61     (str3 = str1) = str2;
    62     cout << "赋值后str3的内容为:" << endl;
    63     str3.show();
    64     return 0;
    65 }

      运行结果:

      

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

      总结

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

      

  • 相关阅读:
    Cookie天使还是恶魔?
    Nhibernate学习起步之manytoone篇
    共享终结者ShareKiller
    基于弹性碰撞原理的抖动式窗口
    Nhibernate分析之华山论剑篇
    Nhibernate学习之manytomany篇
    JavaScript常用字符串函数
    让全中国人蒙羞的搜索爬虫
    近期项目的一些代码总结
    Nhibernate学习之性能改善1
  • 原文地址:https://www.cnblogs.com/codingmengmeng/p/5871254.html
Copyright © 2011-2022 走看看