C++運算子過載規則詳解
為什麼要對運算子進行過載:
C++預定義中的運算子的操作物件只侷限於基本的內建資料型別,但是對於我們自定義的型別(類)是沒有辦法操作的。但是大多時候我們需要對我們定義的型別進行類似的運算,這個時候就需要我們對這麼運算子進行重新定義,賦予其新的功能,以滿足自身的需求。
C++運算子過載的實質:
運算子過載的實質就是函式過載或函式多型。運算子過載是一種形式的C++多型。目的在於讓人能夠用同名的函式來完成不同的基本操作。要過載運算子,需要使用被稱為運算子函式的特殊函式形式
過載運算子的兩種形式:
過載運算子有兩種方式,即:
過載為類的成員函式||過載為類的非成員函式。
過載為類的非成員函式的時候:
通常我們都將其宣告為友元函式,因為大多數時候過載運算子要訪問類的私有資料,(當然也可以設定為非友元非類的成員函式。但是非友元又不是類的成員函式是沒有辦法直接訪問類的私有資料的),如果不宣告為類的友元函式,而是通過在此函式中呼叫類的公有函式來訪問私有資料會降低效能。所以一般都會設定為類的友元函式,這樣我們就可以在此非成員函式中訪問類中的資料了。
運算子過載的形式:
運算子函式過載一般有兩種形式:過載為類的成員函式和過載為類的非成員函式。非成員函式通常是友元。(可以把一個運算子作為一個非成員、非友元函式過載。但是,這樣的運算子函式訪問類的私有和保護成員時,必須使用類的公有介面中提供的設定資料和讀取資料的函式,呼叫這些函式時會降低效能。可以內聯這些函式以提高效能。)
1) 成員函式運算子
運算子過載為類的成員函式的一般格式為:
<函式型別> operator <運算子>(<引數表>)
{
<函式體>
}
當運算子過載為類的成員函式時,函式的引數個數比原來的運算元要少一個(後置單目運算子除外),這是因為成員函式用this指標隱式地訪問了類的一個物件,它充當了運算子函式最左邊的運算元。因此:
(1) 雙目運算子過載為類的成員函式時,函式只顯式說明一個引數,該形參是運算子的右運算元。
(2) 前置單目運算子過載為類的成員函式時,不需要顯式說明引數,即函式沒有形參。
呼叫成員函式運算子的格式如下:
<物件名>.operator <運算子>(<引數>)
它等價於
<物件名><運算子><引數>
例如:a+b等價於a.operator +(b)。一般情況下,我們採用運算子的習慣表達方式。
2) 友元函式運算子
運算子過載為類的友元函式的一般格式為:
friend <函式型別> operator <運算子>(<引數表>)
{
<函式體>
}
當運算子過載為類的友元函式時,由於沒有隱含的this指標,因此運算元的個數沒有變化,所有的運算元都必須通過函式的形參進行傳遞,函式的引數與運算元自左至右一一對應。
呼叫友元函式運算子的格式如下:
operator <運算子>(<引數1>,<引數2>)
它等價於
<引數1><運算子><引數2>
例如:a+b等價於operator +(a,b)。
兩種過載形式的比較
在多數情況下,將運算子過載為類的成員函式和類的友元函式都是可以的。但成員函式運算子與友元函式運算子也具有各自的一些特點:
(1) 一般情況下,單目運算子最好過載為類的成員函式;雙目運算子則最好過載為類的友元函式。
(2) 以下一些雙目運算子不能過載為類的友元函式:=、()、[]、->。
(3) 型別轉換函式只能定義為一個類的成員函式而不能定義為類的友元函式。
(4) 若一個運算子的操作需要修改物件的狀態,選擇過載為成員函式較好。
(5) 若運算子所需的運算元(尤其是第一個運算元)希望有隱式型別轉換,則只能選用友元函式。
(6) 當運算子函式是一個成員函式時,最左邊的運算元(或者只有最左邊的運算元)必須是運算子類的一個類物件(或者是對該類物件的引用)。如果左邊的運算元必須是一個不同類的物件,或者是一個內部型別的物件,該運算子函式必須作為一個友元函式來實現。
(7) 當需要過載運算子具有可交換性時,選擇過載為友元函式。
C++允許過載的運算子和不允許過載的運算子:
C++中絕大部分的運算子允許過載,具體規定見表
不能過載的運算子只有5個:
. (成員訪問運算子)
.* (成員指標訪問運算子)
:: (域運算子)
sizeof (長度運算子)
?: (條件運算子)
typeid(一個RTTI運算子),const_cast、dynamic_cast、reinterpret_cast、static_cast強制型別轉換運算子
前兩個運算子不能過載是為了保證訪問成員的功能不能被改變,域運算子和sizeof 運算子的運算物件是型別而不是變數或一般表示式,不具備過載的特徵。
大多數運算子可以通過成員函式和非成員函式進行過載但是下面這四種運算子只能通過成員函式進行過載:
= 賦值運算子,()函式呼叫運算子,[ ]下標運算子,->通過指標訪問類成員的運算子。
C++運算子過載的規則
C++對運算子過載定義瞭如下幾條規則。
1) C++不允許使用者自己定義新的運算子,只能對已有的C++運算子進行過載。 例如,有人覺得BASIC中用““作為冪運算子很方便,也想在C++中將”“定義為冪運算子,用”3**5“表示35,這樣是不行的。
2) 過載不能改變運算子運算物件(即搡作數)的個數。如關係運算符“>”和“ <” 等是雙目運算子,過載後仍為雙目運算子,需要兩個引數。運算子“ +”,“-”,“*”,“&”等既可以作為單目運算子,也可以作為雙目運算子,可以分別將它們過載為單目運算子或雙目運算子。
3) 過載不能改變運算子的優先級別。例如“*”和“/”優先於“ +”和“-”,不論怎樣進行過載,各運算子之間的優先級別不會改變。有時在程式中希望改變某運算子的優先順序,也只能使用加圓括號的辦法強制改變過載運算子的運算順序。
4) 過載不能改變運算子的結含性。如賦值運算子是右結合性(自右至左),過載後仍為右結合性。
5) 過載運算子的函式不能有預設的引數,否則就改變了運算子引數的個數,與前面第(2)點矛盾。
6) 過載的運算子必須和使用者定義的自定義型別的物件一起使用,其引數至少應有一個是類物件(或類物件的引用)。也就是說,引數不能全部是C++的標準型別,以防止使用者修改用於標準型別資料的運算子的性質,如下面這樣是不對的:
int operator + (int a,int b)
{
retum(a-b);
}
原來運算子+的作用是對兩個數相加,現在企圖通過過載使它的作用改為兩個數相減。 如果允許這樣過載的話,如果有表示式4+3,它的結果是7呢還是1?顯然,這是絕對禁止的。
如果有兩個引數,這兩個引數可以都是類物件,也可以一個是類物件,一個是C ++標準型別的資料,如
Complex operator + (int a,Complex&c)
{
return Complex(a +c.real, c.imag);
}
它的作用是使一個整數和一個複數相加。
7) 用於類物件的運算子一般必須過載,但有兩個例外,運算子“=”和“&”不必過載。
①賦值運算子( = )可以用於每一個類物件,可以利用它在同類物件之間相互賦值。 我們知道,可以用賦值運算子對類的物件賦值,這是因為系統已為每一個新宣告的類過載了一個賦值運算子,它的作用是逐個複製類的資料成員。使用者可以認為它是系統提供的預設的物件賦值運算子,可以直接用於物件間的賦值,不必自己進行過載。但是有時系統提供的預設的物件賦值運算子不能滿足程式的要求,例如,資料成員中包含指向動態分配記憶體的指標成員時,在複製此成員時就可能出現危險。在這種情況下, 就需要自己過載賦值運算子。
②地址運算子&也不必過載,它能返回類物件在記憶體中的起始地址。
8) 從理論上說,可以將一個運算子過載為執行任意的操作,如可以將加法運算子過載為輸出物件中的資訊,將“>”運算子過載為“小於”運算。但這樣違背了運算子過載的初衷,非但沒有提髙可讀性,反而使人莫名其妙,無法理解程式。應當使過載運算子的功能類似於該運算子作用於標準型別資料時所實現的功能(如用“+”實現加法,用“>”實現“大於”的關係運算)。
9) 運算子過載函式可以是類的成員函式,也可以是類的友元函式,還可以是既非類的成員函式也不是友元函敝的普通函式。
我們什麼時候宣告為成員函式,什麼時候宣告為非成員函式呢?
首先,我們要明白這句話:對於成員函式來說,一個運算元通過this指標隱式的傳遞,(即本身),另一個運算元作為函式的引數顯示的傳遞;對於友元函式(非成員函式)兩個運算元都是通過引數來傳遞的。
(1)一般來說,彈幕運算子過載為類的成員函式,雙目運算子過載為類的友元函式(咳咳,一般情況下)
(2)雙目運算子不能將 = 。 ()【】。-> 過載為類的友元函式。
(3)如果運算子的第一次運算元要求為隱式轉換則必須為友元函式。
(4)當最左邊的要求為類物件,而右邊的是一個內建型別,則要為友元函式。