博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
c++中有些重载运算符为什么要返回引用
阅读量:5102 次
发布时间:2019-06-13

本文共 8652 字,大约阅读时间需要 28 分钟。

 

 

  事实上,我们的重载运算符返回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 
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 
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;//这样OK61 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;//这样不OK10     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 
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 
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)

 

  总结

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

  

转载于:https://www.cnblogs.com/codingmengmeng/p/5871254.html

你可能感兴趣的文章
待整理
查看>>
iOS 6
查看>>
Nginx入门篇-基础知识与linux下安装操作
查看>>
一次动态sql查询订单数据的设计
查看>>
C# 类(10) 抽象类.
查看>>
1.linux ping:unknown host www.***.***
查看>>
无向图求桥 UVA 796
查看>>
Nginx+Keepalived 实现双击热备及负载均衡
查看>>
五分钟搭建WordPress博客(二)
查看>>
Vue_(组件通讯)子组件向父组件传值
查看>>
jvm参数
查看>>
Something-Summary
查看>>
Spring学习笔记
查看>>
6个有用的MySQL语句
查看>>
linux c/c++ IP字符串转换成可比较大小的数字
查看>>
我对前端MVC的理解
查看>>
Silverlight实用窍门系列:19.Silverlight调用webservice上传多个文件【附带源码实例】...
查看>>
2016.3.31考试心得
查看>>
mmap和MappedByteBuffer
查看>>
Linux的基本操作
查看>>