c++中有些重载运算符为什么要返回引用
2016-09-14 11:31
489 查看
事实上,我们的重载运算符返回void、返回对象本身、返回对象引用都是可以的,并不是说一定要返回一个引用,只不过在不同的情况下需要不同的返回值。
那么什么情况下要返回对象的引用呢?
原因有两个:
允许进行连续赋值
防止返回对象(返回对象也可以进行连续赋值)的时候调用拷贝构造函数和析构函数导致不必要的开销,降低赋值运算符等的效率。
对于第二点原因:如果用”值传递“的方式,虽然功能任然正确,但由于return语句要把*this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,会降低赋值函数的效率。
场景:
需要返回对象引用或者返回对象(效率没有返回引用高),需要实现连续赋值,使重载的运算符更符合C++本身的运算符语意,如连续赋值 = += -= *= 、=,<<输出流
关于赋值 =,我们知道赋值=有连续等于的特性
同样有趣的是,赋值采用的是右结合律,所以上述连锁赋值被解析为:
这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x。
为了实现”连锁赋值“,赋值操作符号必须返回一个reference(引用)指向操作符号的左侧实参(而事实上重载运算符的左侧实参就是调用对象本身,比如= += -=等),这是你为classes实现赋值操作符时应该遵循的协议:这个协议不仅仅适用于以上的标准赋值形式,也适用于所有赋值运算。
注意,这只是个协议,并无强制性,如果不遵循它,代码一样可以通过编译,然而这份协议被所有内置类型和标准程序库提供的类型入string,vector,complex,trl:shared_ptr或者即将提供的类型共同遵守。因此除非你有一个标新立异的好理由,不然还是随众吧。
下面看一个赋值运算符重载的例子:
1、首先是返回对象的情况:
运行结果:
2、下面是返回引用的情况(String& operator+(const String& str)),直接贴运行结果:
当运算符重载返回的是对象时,会在赋值运算过程的返回途中调用两次拷贝构造函数和析构函数(因为return的是个新的对象)
如果采用String& operator+(const String& str)这样就不会有多余的调用(因为这里直接return一个已经存在的对象的引用)
上面的栗子也说明一点:析构函数的调用是在变量作用域结束的时候(以及程序运行结束的时候)
如果采用return对象,那么第二次赋值运算调用的情况就是:
将一个新的String对象传递到operator+()的参数中去 相当于
如果采用return对象引用,那么第二次赋值运算的情况就是:
将一个已经存在的String对象(其实就是str1)的引用传递给operator+()的参数中去
+=等运算符也是同样的考虑,比如
如果要这样使用+=或其它上面举出的运算符,则这些运算符的返回值一定要是一个对象或者引用才行,不然就会出现错误(参数类型不符合)。什么意思呢,下面举个栗子。
我现在让运算符重载返回的类型为空,单个赋值,不使用连续赋值:
运行结果:
但当我把主函数中str1,str2,str3改为连续赋值时:
出错:
所以,当你确定不使用连续赋值时,直接返回void也是可以的。要明白一点:
运算符左侧的对象就是操作对象,比如
最后要说明一点:并非必须返回引用,返回引用的好处既可以避免拷贝构造函数和析构函数的调用,又可以保证= +=等运算符的原始语义清晰。
啥叫原始语义清晰呢?
如
我们的意识里,就是先执行括号内容,即str1赋值给str3,然后str2再赋值给str3,最后str3输出的内容是str2的。
即如果运算符重载返回的是对象引用时,
运行结果:
str3得到了str2的内容,与我们认识的‘=’运算符逻辑相符。
而如果运算符重载返回的是对象时,
运行结果:
str3只得到了str1的内容,并没有得到str2的内容,这是因为执行(str3=str1)后,因为返回的是对象(一个临时对象,str3的一个拷贝),不是引用,所以此时str3不在后面的‘()=str2’的操作中,而是str2对一个临时对象赋值,所以str3的内容保持不变(等于str1)。
总结
所以,对此类运算符重载时,还是老老实实的返回引用,少搞事,做个好男孩:)
那么什么情况下要返回对象的引用呢?
原因有两个:
允许进行连续赋值
防止返回对象(返回对象也可以进行连续赋值)的时候调用拷贝构造函数和析构函数导致不必要的开销,降低赋值运算符等的效率。
对于第二点原因:如果用”值传递“的方式,虽然功能任然正确,但由于return语句要把*this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,会降低赋值函数的效率。
场景:
需要返回对象引用或者返回对象(效率没有返回引用高),需要实现连续赋值,使重载的运算符更符合C++本身的运算符语意,如连续赋值 = += -= *= 、=,<<输出流
关于赋值 =,我们知道赋值=有连续等于的特性
int x,y,z; x=y=z=15;
同样有趣的是,赋值采用的是右结合律,所以上述连锁赋值被解析为:
x=(y=(z=15));//赋值连锁形式
这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x。
为了实现”连锁赋值“,赋值操作符号必须返回一个reference(引用)指向操作符号的左侧实参(而事实上重载运算符的左侧实参就是调用对象本身,比如= += -=等),这是你为classes实现赋值操作符时应该遵循的协议:这个协议不仅仅适用于以上的标准赋值形式,也适用于所有赋值运算。
class Widght{ public: ..... Widget& operator=(cosnt Widget& rhs) { ... return* this; } Widget& operator+=(cosnt Widget& rhs) { ... return* this; } Widget& operator-=(cosnt Widget& rhs) { ... return* this; } Widget& operator*=(cosnt Widget& rhs) { ... return* this; } Widget& operator/=(cosnt Widget& rhs) { ... return* this; } ... };
注意,这只是个协议,并无强制性,如果不遵循它,代码一样可以通过编译,然而这份协议被所有内置类型和标准程序库提供的类型入string,vector,complex,trl:shared_ptr或者即将提供的类型共同遵守。因此除非你有一个标新立异的好理由,不然还是随众吧。
下面看一个赋值运算符重载的例子:
1、首先是返回对象的情况:
#include <iostream> using namespace std; class String { private: char *str; int len; public: String(const char* s);//构造函数声明 String operator=(const String& another);//运算符重载,此时返回的是对象 void show() { cout << "value = " << str << endl; } /*copy construct*/ String(const String& other) { len = other.len; str = new char[len + 1]; strcpy(str, other.str); cout << "copy construct" << endl; } ~String() { delete[] str; cout << "deconstruct" << endl; } }; String::String(const char* s)//构造函数定义 { len = strlen(s); str = new char[len + 1]; strcpy(str, s); } String String::operator=(const String &other)//运算符重载 { if (this == &other) return *this; // return; delete[] str; len = other.len; str = new char[len + 1]; strcpy(str, other.str); return *this; // return; } int main() { String str1("abc"); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); str3 = str1 = str2;//str3.operator=(str1.operator=str2) str3.show(); str1.show(); return 0; }
运行结果:
2、下面是返回引用的情况(String& operator+(const String& str)),直接贴运行结果:
当运算符重载返回的是对象时,会在赋值运算过程的返回途中调用两次拷贝构造函数和析构函数(因为return的是个新的对象)
如果采用String& operator+(const String& str)这样就不会有多余的调用(因为这里直接return一个已经存在的对象的引用)
上面的栗子也说明一点:析构函数的调用是在变量作用域结束的时候(以及程序运行结束的时候)
如果采用return对象,那么第二次赋值运算调用的情况就是:
将一个新的String对象传递到operator+()的参数中去 相当于
const String&str = returnStringObj;
如果采用return对象引用,那么第二次赋值运算的情况就是:
将一个已经存在的String对象(其实就是str1)的引用传递给operator+()的参数中去
const String&str = returnReference; //(String& returnReference = str1;)
+=等运算符也是同样的考虑,比如
int main() { String str1("abc"); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); str3 = str1 = str2;//str3.operator=(str1.operator=str2) str3.show(); str1.show(); int num = 10; num += (num += 100); cout << num << endl; return 0; }
如果要这样使用+=或其它上面举出的运算符,则这些运算符的返回值一定要是一个对象或者引用才行,不然就会出现错误(参数类型不符合)。什么意思呢,下面举个栗子。
我现在让运算符重载返回的类型为空,单个赋值,不使用连续赋值:
#include <iostream> using namespace std; class String { private: char *str; int len; public: String(const char* s);//构造函数声明 void operator=(const String& another);//运算符重载,此时返回为空 void show() { cout << "value = " << str << endl; } /*copy construct*/ String(const String& other) { len = other.len; str = new char[len + 1]; strcpy(str, other.str); cout << "copy construct" << endl; } ~String() { delete[] str; cout << "deconstruct" << endl; } }; String::String(const char* s) { len = strlen(s); str = new char[len + 1]; strcpy(str, s); } void String::operator=(const String &other) { if (this == &other) // return *this; return; delete[] str; len = other.len; str = new char[len + 1]; strcpy(str, other.str); // return *this; return; } int main() { String str1("abc"); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); str3 = str1;//这样OK str3.show(); str1.show(); return 0; }
运行结果:
但当我把主函数中str1,str2,str3改为连续赋值时:
int main() { String str1("abc"); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); str3 = str1=str2;//这样不OK str3.show(); str1.show(); return 0; }
出错:
所以,当你确定不使用连续赋值时,直接返回void也是可以的。要明白一点:
运算符左侧的对象就是操作对象,比如
ObjectA = ObjectB 等同于ObjectA.operator=(ObjectB) ObjectA+=ObjectB 等同于ObjectA.operator+(ObjectB)
最后要说明一点:并非必须返回引用,返回引用的好处既可以避免拷贝构造函数和析构函数的调用,又可以保证= +=等运算符的原始语义清晰。
啥叫原始语义清晰呢?
如
(str3 = str1) = str2;
我们的意识里,就是先执行括号内容,即str1赋值给str3,然后str2再赋值给str3,最后str3输出的内容是str2的。
即如果运算符重载返回的是对象引用时,
//返回的是对象引用的情况 #include <iostream> using namespace std; class String { private: char *str; int len; public: String(const char* s);//构造函数声明 String& operator=(const String& another);//运算符重载,此时返回为空 void show() { cout << "value = " << str << endl; } /*copy construct*/ String(const String& other) { len = other.len; str = new char[len + 1]; strcpy(str, other.str); cout << "copy construct" << endl; } ~String() { delete[] str; cout << "deconstruct" << endl; } }; String::String(const char* s) { len = strlen(s); str = new char[len + 1]; strcpy(str, s); } String& String::operator=(const String &other) { if (this == &other) return *this; // return; delete[] str; len = other.len; str = new char[len + 1]; strcpy(str, other.str); return *this; // return; } int main() { String str1("abc"); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); (str3 = str1) = str2; cout << "str3的内容为:" << endl; str3.show(); return 0; }
运行结果:
str3得到了str2的内容,与我们认识的‘=’运算符逻辑相符。
而如果运算符重载返回的是对象时,
//这是返回类型为对象的情况 #include <iostream> using namespace std; class String { private: char *str; int len; public: String(const char* s);//构造函数声明 String operator=(const String& another);//运算符重载,此时返回为空 void show() { cout << "value = " << str << endl; } /*copy construct*/ String(const String& other) { len = other.len; str = new char[len + 1]; strcpy(str, other.str); cout << "copy construct" << endl; } ~String() { delete[] str; cout << "deconstruct" << endl; } }; String::String(const char* s) { len = strlen(s); str = new char[len + 1]; strcpy(str, s); } String String::operator=(const String &other) { if (this == &other) return *this; // return; delete[] str; len = other.len; str = new char[len + 1]; strcpy(str, other.str); return *this; // return; } int main() { String str1("abc"); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); (str3 = str1) = str2; cout << "赋值后str3的内容为:" << endl; str3.show(); return 0; }
运行结果:
str3只得到了str1的内容,并没有得到str2的内容,这是因为执行(str3=str1)后,因为返回的是对象(一个临时对象,str3的一个拷贝),不是引用,所以此时str3不在后面的‘()=str2’的操作中,而是str2对一个临时对象赋值,所以str3的内容保持不变(等于str1)。
总结
所以,对此类运算符重载时,还是老老实实的返回引用,少搞事,做个好男孩:)
相关文章推荐
- c++中有些重载运算符为什么要返回引用?
- 为什么C++中千万不要返回局部对象或变量的引用和指针
- c++赋值运算符为什么要返回引用?
- c++的某些操作符为什么要返回引用?为什么经常有书中说对于一些操作符我们要尊重内置类的操作符,所以要返回引用
- c++ 重载 = 为什么返回引用类型
- 为什么C++中千万不要返回局部对象或变量的引用和指针
- c++赋值运算符为什么要返回引用?
- C++赋值运算符重载,为什么要返回引用
- c++赋值运算符为什么要返回引用?
- c++中重载输出操作符,为什么要返回引用
- c++为什么重载输出流符号的时候一定要返回引用
- C++中赋值操作符为什么要返回引用类型
- C++赋值运算符重载,为什么要返回引用?
- c++ 函数返回引用/值
- C++中引用返回和标准返回的区别
- 了解临时对象的来源 (深刻理解为什么不能返回一个临时变量的引用)
- 关于C++中函数返回引用的讨论
- c++对象成员函数返回自身引用时出现的诡异问题及解决办法
- C++函数返回引用
- 为什么operator=操作符返回引用