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

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

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

原因有两个:

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

  

  对于第二点原因:如果用”值传递“的方式,虽然功能任然正确,但由于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,trl:shared_ptr或者即将提供的类型共同遵守。因此除非你有一个标新立异的好理由,不然还是随众吧。

  下面看一个赋值运算符重载的例子:

  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对象传递到operator+()的参数中去 相当于

const String&str = returnStringObj;

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

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

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)。

  总结

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

  

时间: 2024-10-12 09:06:21

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

C++ 重载运算符返回值 和 返回引用的原因

原因是: +,-,*等返回不了引用,比如+运算符,可以如下重载(为了简单,假设A 只有int x:int y) A operator+(A a,A b) {A sum;   sum.x=a.x+b.x; sum.y=a.y+b.y;   return sum; } 分析不能用引用的原因: 函数传入了两个参数a,b,并且+只能传入两个参数(c++不允许自己创造运算符),这样就限定了和sum不能作为参数传入,所以sum只能是一个函数里的临时变量,但是临时变量在函数结束时是要销毁的,那么函数调用玩,s

C++中不可重载5个运算符

C++中不可重载的5个运算符 C++中的大部分运算符都是可以重载的,只有以下5个运算符不可以重载,他们是: 1  .(点运算符)通常用于去对象的成员,但是->(箭头运算符),是可以重载的 2  ::(域运算符)即类名+域运算符,取成员,不可以重载 3  .*(点星运算符,)不可以重载,成员指针运算符".*,即成员是指针类型 4  ?:(条件运算符)不可以重载 5  sizeof不可以重载

C++中关于指针运算符-&gt;的重载问题

#include<iostream>using namespace std;struct date{ int year; int month; int day;};struct Person{ string name; int age; bool gender; double salary; date birth; Person() {  cout<<"创建persond对象"<<this<<endl;  age=10; } ~Perso

C++中重载运算符

重载运算符,可以定义运算符为自己想要的效果,简化程序,以重载<运算符为例: #ifndef BOX_H #define BOX_H class Box{ public: Box(double aLength=1.0,double aWidth=1.0,double aHeight=1.0); double volume() const; double getLength() const; double getWidth() const; double getHeight() const; //重

【c++】c++中重载输出操作符,为什么要返回引用

针对:ostream & operator <<(ostream & os, const ClassType &object) 说明几点: 1.第一个形参为对ostream对象的引用,在该对象上将产生输出,ostream为非const,因为写入到流会改变流的状态:该形参是一个引用,因为不能复制ostream对象(在c++中定义的标准输入输出流类istream和ostream,其中拷贝构造函数和赋值操作符函数都被放置在了private部分,且只有声明,没有定义). 2.第

第十七周oj刷题——Problem A: 实现复数类中的加运算符重载【C++运算符重载】

Description int家有i1和i2弟兄俩,小手一拉i1+i2,加起来了:double家有d1和d2姐妹俩,小手也一拉,d1+d2,也加起来了.C++村子里来了复数(Complex)一家子,也有俩兄弟c1和c2,想要来个累加,笨乎乎地,c1.add(c2).c1和c2伤心极了,也想像其他小朋友一样,小手一拉,c1+c2,也能加起来.这个任务交给了正在看题的魔术师,帮他们一个忙,让复数也能用+号相加吧.(可以复制提示部分的代码开始你的编程) Input 四个数,分别代表两个虚数c1和c2的

operator重载运算符

1.重载运算符的函数一般格式如下 函数类型    operator  运算符名称    (形参表列) {对运算符的重载处理} 例如,想将“+”用于Complex(复数)的加法运算,函数的原型可以是这样的: Complex operator + (Complex & c1,Complex &c2); operator+函数表示对运算符+重载.其中,operator是关键字,专门用于定义重载运算符的函数的,运算符名称就是C++提供给用户的预定运算符. 注意:函数名是由operator和运算符组

Python 正确重载运算符

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 有些事情让我不安,比如运算符重载.我决定不支持运算符重载,这完全是个人选择,因为我见过太多 C++ 程序员滥用它. --James Gosling Java 之父 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 运算符重载的作用是让用户定义的对象使用中缀运算符(如 + 和 |)或一元运算

从C过渡到C++须注意的几个知识点(结构体、引用、重载运算符)

一.结构体和类(class) 下面一个使用结构体类型的例子 1 #include <iostream> 2 using namespace std; 3 struct Point{ // 声明Point结构体类型 4 double x; // 成员变量,没有使用private和public时系统默认为公有类型成员变量 5 double y; 6 }; 7 int main() 8 { 9 Point p; // 定义的p在c里称为结构体变量 10 p.x = 3.2; // 因为x是公有类型可