0、引言
過載,顧名思義從字面上理解就是重複裝載,打一個不恰當的比方,你可以用一個籃子裝蔬菜,也可以裝水果或者其它,使用的是同一個籃子,但是可以用籃子重複裝載的東西不一樣。
正如在之前的文章《過載的奧義之函式過載》中介紹的類似,函式的過載是指利用相同的函式名設計一系列功能相近,但是功能細節不一樣的函式介面;因此運算子過載也是指對於同一個運算子來說,它可以用於實現不同的功能。下面就一起來理解下運算子過載的應用。
1、運算子過載定義
正常來說,我們一般使用的運算子是對基本的資料型別進行操作,但是在C++中有了物件,導致物件無法透過運算子進行運算,故引入了運算子過載即需要重新的定義這些運算子,賦予已有運算子新的功能,使它能夠用於特定型別執行特定的操作。運算子過載的實質是函式過載,它提供了C++的可擴充套件性。
運算子過載是透過建立運算子函式實現的,運算子函式定義了過載的運算子將要進行的操作。運算子函式的定義與其他函式的定義類似,唯一的區別是運算子函式的函式名是由關鍵字operator和其後要過載的運算子符號構成的。運算子函式定義的一般格式如下:
其中,“返回型別說明符”指出過載運算子的返回值型別,operator是定義運算子過載函式的關鍵字,“運算子符號”指出要過載的運算子名字,是C++中可過載的運算子,比如要過載加法運算子,這裡直接寫“+”即可,“參數列”指出過載運算子所需要的引數及其型別。可以看出,運算子過載是一種形式C++多型的體現。
例如,使用“+”將兩個物件相加,編譯器將根據運算元的數目和型別決定使用哪種加法定義,這樣可以讓程式碼看起來更加自然。
運算子過載就是對已有的運算子重新進行定義,賦予其另一種功能,以達到適應不同的資料型別。運算子過載不能改變它本來的寓意(也就是加法不能變更為減法),運算子過載只是一種 “語法上的方便” ,它只是一種函式呼叫的方式。
2、作為成員函式進行過載
我們就以“+”運算子過載舉例:
執行結果為:
從上面的例子可以看出,在addfloat類中對“+”運算子進行了過載 ,過載後可以對該類的物件進行加法運算。當執行 t = m + n時,編譯器檢測到“+”左邊的m(“+”具有左結合性,所以先檢測左邊)是一個 addfloat類物件,就會呼叫成員函式 operator+(),將表示式轉換成如下格式:
t = m.operator + (n);
表示式中m作為呼叫函式的物件,n作為函式的實參。
3、作為全域性函式進行過載
對於之前的例子: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+()函式。
這種場景下針對“+”這種運算子作為類的成員函式進行過載是不可以的。運算子過載不僅僅可以透過類的成員函式來實現,也可以透過全域性函式來實現。
我們需要將運算子過載的全域性函式宣告為友元函式,因為大多數時候過載運算子要訪問類的私有資料,(當然也可以設定為非友元非類的成員函式,但是非友元又不是類的成員函式,是沒有辦法直接訪問類的私有資料的),如果不宣告為類的友元函式,而是透過在此函式中呼叫類的公有函式來訪問私有資料會降低效能。所以一般都會設定為類的友元函式,這樣我們就可以在此非成員函式中訪問類中的資料了。
由上述程式可以看出,運算子過載函式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);
以全域性函式的形式過載“+”,是為了保證“+”運算子的運算元能夠被對稱的處理;換句話說,常數在“+”左邊和右邊都是正確的;
因此,運算子左右兩邊都有操作物件時,且這兩個操作物件可以互換,最好可以使用全域性函式的形式過載,例如:+、-、*、/、==、!= ,這些符合運算子兩邊有操作物件的運算子。
4、運算子過載的一些規則
(1)可以過載的運算子
(2)不可以過載的運算子
. (成員訪問運算子)
.* (成員指標訪問運算子)
:: (域運算子)
sizeof (長度運算子)
?: (條件運算子)
(3) 只能以成員函式的形式過載的運算子(與 this關聯太多)
= (賦值運算子)
() (函式呼叫運算子)
[] (下標運算子)
-> (成員訪問運算子)
(4)只能以全域性函式過載的運算子
<< (左移運算子)
>> (右移運算子)
(5)運算子過載函式既可以作為類的成員函式,也可以作為全域性函式。友元函式運算子過載函式與成員運算子過載函式的區別是:友元函式沒有this指標,而成員函式有,因此,在兩個運算元的過載中友元函式有兩個引數,而成員函式只有一個。
(6)有一部分運算子過載既可以是成員函式也可以是全域性函式,雖然沒有一個必然的、不可抗拒的理由選擇成員函式,但我們應該優先考慮成員函式,這樣更符合運算子過載的初衷。
(7)對於複合的賦值運算子如 +=、-=、*=、/=、&=、!=、~=、%=、>>=、<<= 建議過載為成員函式;
單目運算子最好過載為成員函式;
對於其它運算子,建議過載為全域性函式。
(8)使用運算子不能違反運算子原來的語法規則,原來有幾個運算元、運算元在左邊還是在右邊,這些都不會改變。算符過載函式不能有預設的引數,否則就改變了運算子運算元的個數。
(9)運算子的優先順序不能被過載改變。然而,圓括號能夠強制改變表示式中過載運算子的求值順序。
(10)運算子的結合性不能被過載改變。如果一個運算子的結合性是從左向右,那麼,它的所有過載的版本的結合性依然是從左向右
(11)不能創造新的運算子,即只能過載現有的運算子。例如不能定義operator** (···)來表示求冪。
(12)過載的運算子必須和使用者定義的物件一起使用,運算子引數(操作的物件)中至少應有一個是類物件(或類物件的引用)。
↓↓↓更多技術內容和書籍資料獲取,入群技術交流敬請關注“明解嵌入式”↓↓↓