過載,顧名思義從字面上理解就是重複裝載,打一個不恰當的比方,你可以用一個籃子裝蔬菜,也可以裝水果或者其它,使用的是同一個籃子,但是可以用籃子重複裝載的東西不一樣。
正如在之前的文章《過載的奧義之函數過載》中介紹的類似,函數的過載是指利用相同的函數名設計一系列功能相近,但是功能細節不一樣的函數介面;因此運運算元過載也是指對於同一個運運算元來說,它可以用於實現不同的功能。下面就一起來理解下運運算元過載的應用。
正常來說,我們一般使用的運運算元是對基本的資料型別進行操作,但是在C++中有了物件,導致物件無法通過運運算元進行運算,故引入了運運算元過載即需要重新的定義這些運運算元,賦予已有運運算元新的功能,使它能夠用於特定型別執行特定的操作。運運算元過載的實質是函數過載,它提供了C++的可延伸性。
運運算元過載是通過建立運運算元函數實現的,運運算元函數定義了過載的運運算元將要進行的操作。運運算元函數的定義與其他函數的定義類似,唯一的區別是運運算元函數的函數名是由關鍵字operator和其後要過載的運運算元符號構成的。運運算元函數定義的一般格式如下:
1 <返回型別說明符> operator <運運算元符號>(<參數列>) 2 { 3 <函數體> 4 }
其中,「返回型別說明符」指出過載運運算元的返回值型別,operator是定義運運算元過載函數的關鍵字,「運運算元符號」指出要過載的運運算元名字,是C++中可過載的運運算元,比如要過載加法運運算元,這裡直接寫「+」即可,「參數列」指出過載運運算元所需要的引數及其型別。可以看出,運運算元過載是一種形式C++多型的體現。
例如,使用「+」將兩個物件相加,編譯器將根據運算元的數目和型別決定使用哪種加法定義,這樣可以讓程式碼看起來更加自然。
1 //正常情況下兩個陣列的數相加 2 for(int i= 0; i<10; i++) 3 c[i] = a[i] + b[i]; 4 //可以通過定義一個陣列的類,過載「+」運運算元後 5 //隱藏了內部機制,並強調了實質 6 arry operator+(arry p,arry q) 7 { 8 arry t; 9 for(int i= 0; i<10; i++) //c = a + b; 10 { 11 t.a[i]=p.a[i]+q.a[i]; 12 } 13 return t; 14 }
運運算元過載就是對已有的運運算元重新進行定義,賦予其另一種功能,以達到適應不同的資料型別。運運算元過載不能改變它本來的寓意(也就是加法不能變更為減法),運運算元過載只是一種 「語法上的方便」 ,它只是一種函數呼叫的方式。
我們就以「+」運運算元過載舉例:
1 #include <iostream> 2 using namespace std; 3 class addfloat 4 { 5 public: 6 addfloat(float p); 7 //宣告運運算元過載 8 addfloat operator+(const addfloat &A) const; 9 void show() const; 10 private: 11 float m_p; 12 }; 13 addfloat::addfloat(float p) 14 { 15 m_p = p; 16 } 17 //作為類的成員函數實現運運算元過載 18 addfloat addfloat::operator+(const addfloat &A) const 19 { 20 addfloat B; 21 B.m_p = this->m_p + A.m_p; 22 return B; 23 } 24 void addfloat::show() const 25 { 26 cout<<"輸出結果是"<<m_p<<endl; 27 } 28 29 30 int main() 31 { 32 addfloat m(5.1); 33 addfloat n(1.5); 34 addfloat t; 35 t = m + n; //兩個addfloat類物件相加:t = m.operator+(n); 36 t.show(); 37 return 0; 38 }
執行結果為:
1 輸出結果是6.6
從上面的例子可以看出,在addfloat類中對「+」運運算元進行了過載 ,過載後可以對該類的物件進行加法運算。當執行 t = m + n時,編譯器檢測到「+」左邊的m(「+」具有左結合性,所以先檢測左邊)是一個 addfloat類物件,就會呼叫成員函數 operator+(),將表示式轉換成如下格式:
t = m.operator + (n);
表示式中m作為呼叫函數的物件,n作為函數的實參。
對於之前的例子:t = m + n,m和n是作為addfloat類的物件進行相加的,使用成員函數 operator+()轉換為了t = m.operator+(n),如果n不是類的物件,而是一個常數,例如:
t = m + 5.2;那麼可以轉換t = m.operator+(5.2);
但是如果m是一個常數時,即:t = 5.2 + n;則t = (5.2).operator + (n)這種轉換是不允許的,編譯器會報錯,因為5.2不能作為類的物件呼叫運運算元過載operator+()函數。
這種場景下針對「+」這種運運算元作為類的成員函數進行過載是不可以的。運運算元過載不僅僅可以通過類的成員函數來實現,也可以通過全域性函數來實現。
我們需要將運運算元過載的全域性函數宣告為友元函數,因為大多數時候過載運運算元要存取類的私有資料,(當然也可以設定為非友元非類的成員函數,但是非友元又不是類的成員函數,是沒有辦法直接存取類的私有資料的),如果不宣告為類的友元函數,而是通過在此函數中呼叫類的公有函數來存取私有資料會降低效能。所以一般都會設定為類的友元函數,這樣我們就可以在此非成員函數中存取類中的資料了。
1 #include <iostream> 2 using namespace std; 3 class addfloat 4 { 5 public: 6 addfloat(float p); 7 //宣告為友元函數 8 friend addfloat operator+(const addfloat &A, const addfloat &B); 9 void show() const; 10 private: 11 float m_p; 12 }; 13 addfloat::addfloat(float p) 14 { 15 m_p = p; 16 } 17 18 void addfloat::show() const 19 { 20 cout<<"輸出結果是"<<m_p<<endl; 21 } 22 23 //作為全域性函數進行過載 24 addfloat operator+(const addfloat &A, const addfloat &B) 25 { 26 addfloat C; 27 C.m_p = A.m_p + B.m_p; 28 return C; 29 } 30 31 int main() 32 { 33 addfloat m(5.1); 34 addfloat n(1.5); 35 addfloat t; 36 t = m + n; //兩個addfloat類物件相加:t = m.operator+(n); 37 t.show(); 38 return 0; 39 }
由上述程式可以看出,運運算元過載函數operator+()不是 addfloat類的成員函數,但是卻用到了 addfloat類的 private 成員變數m_p,所以需要在 addfloat類中將operator+()函數宣告為友元函數。
當執行t = m + n時,編譯器檢測到「+」兩邊都是addfloat類的物件,就會轉換為類似下面的函數呼叫:
t = operator + (m, n);
因此,m和n都可以看作是函數的實參:
t = m + 5.2轉換為 t = operator + (m, 5.2);
t = 5.2 + n轉換為 t = operator + (5.2, n);
以全域性函數的形式過載「+」,是為了保證「+」運運算元的運算元能夠被對稱的處理;換句話說,常數在「+」左邊和右邊都是正確的;
因此,運運算元左右兩邊都有操作物件時,且這兩個操作物件可以互換,最好可以使用全域性函數的形式過載,例如:+、-、*、/、==、!= ,這些符合運運算元兩邊有操作物件的運運算元。
(1)可以過載的運運算元
(2)不可以過載的運運算元
. (成員存取運運算元)
.* (成員指標存取運運算元)
:: (域運運算元)
sizeof (長度運運算元)
?: (條件運運算元)
(3) 只能以成員函數的形式過載的運運算元(與 this關聯太多)
= (賦值運運算元)
() (函數呼叫運運算元)
[] (下標運運算元)
-> (成員存取運運算元)
(4)只能以全域性函數過載的運運算元
<< (左移運運算元)
>> (右移運運算元)
(5)運運算元過載函數既可以作為類的成員函數,也可以作為全域性函數。友元函數運運算元過載函數與成員運運算元過載函數的區別是:友元函數沒有this指標,而成員函數有,因此,在兩個運算元的過載中友元函數有兩個引數,而成員函數只有一個。
(6)有一部分運運算元過載既可以是成員函數也可以是全域性函數,雖然沒有一個必然的、不可抗拒的理由選擇成員函數,但我們應該優先考慮成員函數,這樣更符合運運算元過載的初衷。
(7)對於複合的賦值運運算元如 +=、-=、*=、/=、&=、!=、~=、%=、>>=、<<= 建議過載為成員函數;
單目運運算元最好過載為成員函數;
對於其它運運算元,建議過載為全域性函數。
(8)使用運運算元不能違反運運算元原來的語法規則,原來有幾個運算元、運算元在左邊還是在右邊,這些都不會改變。算符過載函數不能有預設的引數,否則就改變了運運算元運算元的個數。
(9)運運算元的優先順序不能被過載改變。然而,圓括號能夠強制改變表示式中過載運運算元的求值順序。
(10)運運算元的結合性不能被過載改變。如果一個運運算元的結合性是從左向右,那麼,它的所有過載的版本的結合性依然是從左向右
(11)不能創造新的運運算元,即只能過載現有的運運算元。例如不能定義operator** (···)來表示求冪。
(12)過載的運運算元必須和使用者定義的物件一起使用,運運算元引數(操作的物件)中至少應有一個是類物件(或類物件的參照)。
更多技術內容和書籍資料獲取,入群技術交流敬請關注「明解嵌入式」
0、引言 過載,顧名思義從字面上理解就是重複裝載,打一個不恰當的比方,你可以用一個籃子裝蔬菜,也可以裝水果或者其它,使用的是同一個籃子,但是可以用籃子重複裝載的東西不一樣。
本文來自部落格園,作者:Sharemaker,轉載請註明原文連結:https://www.cnblogs.com/Sharemaker/p/17327444.html