運算子過載是指允許使用者使用使用者定義的型別編寫表示式的能力。它允許使用者定義的型別與預定義的型別具有相同的功能。
例如,通常需要編寫類似於以下內容的**,以將兩個數字相加。很明顯,sum 是兩個數字之和。
int i = 5;
int sum = i + j;
如果可以使用代表複數的使用者定義的型別來編寫相同型別的表示式,那當然是最好不過了:
complex i = 5;
complex sum = i + j;
運算子過載允許為使用者定義的型別過載(即指定明確的含義)諸如「+」這樣的運算子。如果不進行過載,則使用者需要編寫以下**:
complex i = new complex(5);
complex sum = complex.add(i, j);
此**可以很好地執行,但 complex 型別並不能象語言中的預定義型別那樣發揮作用。
任何事情都有特定的時間和場所
運算子過載是乙個容易引起誤解的語言功能,而且程式設計人員對待它的態度也大相徑庭。一些人認為:使用者使用這一功能編寫的程式將令人費解,而且它也不應歸於程式語言。另一些人則認為它是乙個很不錯的功能,在任何地方都可以使用。
這兩種觀點既包含正確的成分,但也有欠妥之處。應該承認,運算子過載可能會導致編寫出的程式令人費解,但根據我的經驗,即使不使用運算子過載,也很可能編寫出令人費解的**。在某些情況下,不使用過載甚至會使**更加令人費解。
那些不分場合、隨意使用過載的人「確實」在生產令人費解的**。
在語言中之所以使用過載,是為了在概念上對使用者的類或結構進行簡化。只有在有助於提高使用者所寫**的可讀性時,才能對運算子進行過載。請注意,我們所說的檢驗標準是「更清晰」,而不是「更簡短」。運用了運算子過載的類幾乎總是會使**變得更簡短,但並不能每次都使**變得更清晰(即可讀性更強)。
為了說明這一點,我建立了多個過載示例。您需要仔細閱讀這些**,想一想哪個運算子進行了過載,過載的運算子執行了什麼運算。
測驗 1
bignum n1 = new bignum("123456789012345");
bignum n2 = new bignum("11111");
bignum sum = n1 + n2;
b matrix m1 = loadmatrix();
matrix m2 = loadmatrix();
matrix result = m1 * m2;
iii
dbrow row = query.execute();
while (!row.done)
iv account current = findaccount(idnum);
current += 5;
答案和討論
1 本示例中,要執行的運算是顯而易見的。這種加法只不過是將預定義的型別相加,每個人都明白執行了什麼運算,因此在這個示例中,使用運算子過載很有意義。
b 本示例演示了矩陣如何相乘。從概念上來說,矩陣乘法與常規乘法不完全類似,但它是乙個明確定義的運算,因此任何理解矩陣乘法的人看到這種過載的運算子時,都不會感到驚訝。
iii
本示例中,增量 (++) 運算子進行了過載,它使資料庫行向前移至下一行。任何與資料庫行有關的事物都不可能使我們理解這種增量的真正含義,而且,這種增量要執行的運算也不是那麼明顯。
在這一示例中,過載的使用也沒有使**變得更簡單。如果我們轉而使用以下**,情況就好多了:
dbrow row = query.execute();
while (!row.movenext())
iv 將事物和雇員相加代表什麼含義呢?本示例中,選擇是乙個不錯的方法,將其與雇員數相加就會註冊雇員。這是一種很糟糕的運算子過載用法。
原則 何時進行過載的原則是相當簡單的。如果使用者希望能執行這種運算,那麼就應該進行過載。
過載算術運算子
要過載 c# 中的運算子,指定要執行運算的函式就可以了。函式必須在運算所涉及的型別中進行定義,並且至少有乙個引數屬於該型別。這樣可以防止對 int 的加法或其它奇怪事物進行過載。
為了演示過載,我們將開發乙個向量。向量可以被認為是從原點到特定二維點的線。可以對向量執行多種運算。以下是該型別的粗略定義:
struct vector }
要實際使用,向量應支援以下運算:
獲取長度
將向量乘以某個數字
將向量除以某個數字
將兩個向量相加
將乙個向量減去另乙個向量
計算兩個向量的點積
我們的任務是確定應該如何實現這些運算。
長度 對於獲取向量的長度,似乎沒有任何有意義的運算子。長度不會變化,因此將它作為屬性是很有意義的:
public float length }
將向量乘以/除以某個數字
將向量乘以某個數字是相當常見的運算,並且是使用者希望實現的運算。以下是相關**:
public static vector operator*(vector vector, float multiplier)
應該注意,此處有許多有趣的現象。首先,運算子是 static 函式,因此它必須獲取兩個引數的值,同時在結果中必須返回乙個新的物件。運算子的名稱恰好是「operator」,後面緊跟著要過載的運算子。
除以某個數字的**與以上**類似。
將兩個向量進行加減
這是很常見的向量運算,因此很顯然要對它們進行過載。
public static vector operator+(vector vector1, vector vector2)
減法的**與以上**非常類似。
計算點積
兩個向量的點積是為向量定義的特殊運算,在預定義的型別中根本無法找到與之相類似的運算。在方程式中,點積通過在兩個向量之間寫乙個點來表示,因此它和任何現有運算子都不是精確匹配。點積的乙個有趣特徵是:它獲取兩個向量的值,但只返回乙個簡單的數字。
無論是否對該運算進行過載,使用者**都大致相同。第一行顯示了正在使用的過載版本,其它行則顯示了兩個替代版本:
double v1i = (velocity * center) / (t * t);
double v1i = vector.dotproduct(velocity, center) / (t * t);
double v1i = velocity.dotproduct(center) / (t * t);
此時,它幾乎是乙個判斷呼叫。我編寫的類對「*」運算子進行了過載,以便進行點積運算,但回過頭細想一下,我認為這一**並不是最合適的**。
在第乙個示例中,velocity 和 center 是向量這一點並不是很清晰,因此,點積是要執行的運算這一點也不是很清晰(我在查詢乙個使用它的示例時,注意到了這一點)。第二個示例很清楚地說明了要執行什麼運算,我認為使用該示例中的**最合適。
第三個示例也還可以,但我認為,如果該運算不是成員函式的話,**會更清晰一些。
public static double dotproduct(vector v1, vector v2)
c# 和 c++ 過載
與 c++ 相比較,c# 允許過載的運算子很少。有兩條限制。首先,成員訪問、成員呼叫(也就是函式呼叫)、賦值以及「新建」無法過載,因為這些運算是執行時定義的。
其次,諸如「&&」、「||」、「?:」這樣的運算子以及諸如「+=」這樣的復合賦值運算子無法過載,因為這會使**變得異常複雜,得不償失。
過載的轉換
讓我們返回到最初的示例:
complex i = 5;
complex sum = i + j;
雖然知道了如何過載加法運算子,但我們仍需要想方法使第乙個語句發揮作用。這可以通過對轉換進行過載來實現。
隱式和顯式轉換
c# 同時支援隱式和顯式轉換。隱式轉換是那些總是能成功執行的轉換,並且其成功的原因通常是目標型別的範圍等於或大於源型別的範圍。從 short 到 int 的轉換就是乙個隱式轉換。隱式轉換可以作為賦值語句的一部分:
short svalue = 5;
long lvalue = svalue;
顯式轉換是那些可能導致資料丟失或者引發異常的轉換。因此,顯式轉換要求強制進行型別轉換:
long lvalue = 5;
short svalue = (short) lvalue;
對轉換進行過載時,應該決定轉換是隱式還是顯式的,但是,應該明白隱式轉換模型是安全的,而顯式轉換則是有風險的。
將整數值 5 轉換為複數的轉換定義如下所示:
public static implicit operator complex(int value)
這允許進行從 int 到 complex 的隱式轉換。
語言的互操作性
以上是在 c# 中對運算子進行過載的情況。涉及到其它語言時,事情將變得略為複雜。
運算子過載不是 .net 公共語言子集中的功能之一,這意味著在某些語言中將無法使用過載。因此,提供非過載的替代方案是非常重要的,以便在其它語言中仍然能執行相同的運算。如果您的類定義了加法運算子,它還應該定義相同的方法,使用類似 add 這樣的名稱進行命名。
C 運算子過載 過載特殊運算子
賦值運算子用於同類物件間的相互賦值。賦值運算子只能被過載為類的非靜態成員函式,不能過載為友元函式和普通函式。對於使用者自定義的類而言,如果沒有過載賦值運算子,那麼c 編譯器會為該類提供乙個預設的過載賦值運算子成員函式。預設賦值運算子的工作方式是按位對拷,將等到右邊物件的非靜態成員拷貝給等號左邊的物件...
C 運算子過載賦值運算子
自定義類的賦值運算子過載函式的作用與內建賦值運算子的作用類似,但是要要注意的是,它與拷貝建構函式與析構函式一樣,要注意深拷貝淺拷貝的問題,在沒有深拷貝淺拷貝的情況下,如果沒有指定預設的賦值運算子過載函式,那麼系統將會自動提供乙個賦值運算子過載函式。賦值運算子過載函式的定義與其它運算子過載函式的定義是...
C 運算子過載轉換運算子
為什麼需要轉換運算子?大家知道對於內建型別的資料我們可以通過強制轉換符的使用來轉換資料,例如 int 2.1f 自定義類也是型別,那麼自定義類的物件在很多情況下也需要支援此操作,c 提供了轉換運算子過載函式 它使得自定義類物件的強轉換成為可能。轉換運算子的生命方式比較特別,方法如下 operator...