**
事實上,我們的過載運算子返回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
9 widget& operator+=(cosnt widget& rhs)
10 14
15 widget& operator-=(cosnt widget& rhs)
16 20
21 widget& operator*=(cosnt widget& rhs)
22 26
27 widget& operator/=(cosnt widget& rhs)
28 32 …
33 };
複製**
注意,這只是個協議,並無強制性,如果不遵循它,**一樣可以通過編譯,然而這份協議被所有內建型別和標準程式庫提供的型別入string,vector,complex,std::trl::shared_ptr或者即將提供的型別共同遵守。因此除非你有乙個標新立異的好理由,不然還是隨眾吧。
下面看乙個賦值運算子過載的例子:(連續賦值,常規的情況(a = b = c))
1、首先是返回物件的情況:
複製**
1 #include
2 using namespace std;
3 class string
4 15
16 /copy construct/
17 string(const string& other)
18 24
25 ~string()
26 30 };
3132 string::string(const char* s)//建構函式定義
33 38
39 string string::operator=(const string &other)//運算子過載
40 51
52 int main()
53 複製**
執行結果:
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 複製**
如果使用+=或其它上面舉出的運算子進行連續操作時,,則這些運算子的返回值一定要是乙個物件或者引用才行,不然就會出現錯誤(引數型別不符合)。什麼意思呢,下面舉個栗子。
我現在讓運算子過載返回的型別為空,單個賦值,不使用連續賦值:
複製**
1 #include
2 using namespace std;
3 class string
4 15
16 /copy construct/
17 string(const string& other)
18 24
25 ~string()
26 30 };
3132 string::string(const char* s)
33 38
39 void string::operator=(const string &other)
40 51
52 int main()
53 複製**
執行結果:
但當我把主函式中str1,str2,str3改為連續賦值時:
複製**
1 int main()
2 複製**
出錯:所以,當你確定不使用連續賦值時,直接返回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 16
17 /copy construct/
18 string(const string& other)
19 25
26 ~string()
27 31 };
3233 string::string(const char* s)
34 39
40 string& string::operator=(const string &other)
41 52
53 int main()
54 複製**
執行結果:
str3得到了str2的內容,與我們認識的『=』運算子邏輯相符。
而如果運算子過載返回的是物件時,
複製**
1 //這是返回型別為物件的情況
2 #include
3 using namespace std;
4 class string
5 16
17 /copy construct/
18 string(const string& other)
19 25
26 ~string()
27 31 };
3233 string::string(const char* s)
34 39
40 string string::operator=(const string &other)
41 52
53 int main()
54 複製**
執行結果:
str3只得到了str1的內容,並沒有得到str2的內容,這是因為執行(str3=str1)後,因為返回的是物件(乙個臨時物件,str3的乙個拷貝),不是引用,所以此時str3不在後面的『=str2』的操作中,而是str2對乙個臨時物件賦值,所以str3的內容保持不變(等於str1)。
總結所以,對此類運算子過載時,還是老老實實的返回引用,少搞事,做個好男孩:)
C 過載運算子
運算子過載是一種形式的c 多型。在c 中,編譯器有能力把乙個由資料 物件和操作符共同組成的表示式,解釋為對乙個全域性或成員函式的呼叫。該全域性或成員函式被稱為操作符函式,通過重定義操作符函式,可以實現針對自定義型別 結構,類 的運算法則,並使之與內建型別一樣參與各種表示式。過載運算子可使 看起來更加...
C 過載運算子
本文主要講述加號運算子 自增運算子 流提取運算子運 流插入運算子 先給出vector類 class vector 建構函式 vector const vector v 拷貝建構函式 vector operator const vector v 過載 vector operator 過載前置自增運算子...
C 過載運算子
過載的運算子是帶有特殊名稱的函式,函式名是由關鍵字 operator 和其後要過載的運算子符號構成的。與其他函式一樣,過載運算子有乙個返回型別和乙個引數列表。如果我們定義的函式為類的成員函式 box operator const box 如果我們定義的函式為非成員函式,那麼我們需要為每次操作傳遞兩個...