c++中有些重载运算符为什么要返回引用

转自https://www.cnblogs.com/codingmengmeng/p/5871254.html
https://blog.csdn.net/tmxk13/article/details/78496676
事实上,我们的重载运算符返回void、返回对象本身、返回对象引用都是可以的,并不是说一定要返回一个引用,只不过在不同的情况下需要不同的返回值。

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

原因有两个:
1.允许进行连续赋值
2.防止返回对象(返回对象也可以进行连续赋值(常规的情况,如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;//这样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
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)。

总结

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值