c++中有些重載運算符為什么要返回引用


 

  事實上,重載運算符返回void、返回對象本身、返回對象引用都是可以的,並不是說一定要返回一個引用,只不過在不同的情況下需要不同的返回值。

那么什么情況下要返回對象的引用呢?

原因有兩個:

  •   允許進行連續賦值
  •     防止返回對象(返回對象也可以進行連續賦值(常規的情況,如a = b = c,而不是(a = b) = c))的時候調用拷貝構造函數和析構函數導致不必要的開銷,降低賦值運算符的效率。

  

  對於第二點原因:如果用”值傳遞“的方式,雖然功能仍然正確,但由於return語句要把*this拷貝到保存返回值的外部存儲單元之中,增加了不必要的開銷,會降低賦值函數的效率

 場景:

  需要返回對象引用或者返回對象(效率沒有返回引用高),需要實現連續賦值,使重載的運算符更符合C++本身的運算符語意,如連續賦值 = += -= *= 、=,<<輸出流

  關於賦值 =,我們知道賦值=有連續等於的特性

 int x,y,z; 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,std::trl::shared_ptr或者即將提供的類型共同遵守。因此除非你有一個標新立異的好理由,不然還是隨眾吧。

 

  下面看一個賦值運算符重載的例子:(連續賦值,常規的情況(a = b = c)

  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對象(returnStringObj)傳遞到operator = (const String& str)的參數中去 相當於 

const String&str = returnStringObj;

 如果采用return對象引用,那么第二次賦值運算的情況就是

  將一個已經存在的String對象的引用((其實就是str1))傳遞給operator = (const String& str)的參數中去

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; }

出錯:IntelliSense:沒有與這些操作數匹配的“=”運算符

        操作數類型為:String=void

 所以,當確定不使用連續賦值時,直接返回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)。

 

總結

  所以,對此類運算符重載時,還是老老實實的返回引用,不要瞎搞,做個乖孩子

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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