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)

 

  總結

  所以,對此類運算符重載時,還是老老實實的返回引用,少搞事,做個好男孩:)

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM