運算子過載

2021-04-17 01:07:42 字數 4323 閱讀 5537

eric gunnerson

microsoft corporation

2023年6月21日

作為有關 c# 語言規範漫談的繼續,本月我們將討論運算子過載的問題。運算子過載(除非特別指明,否則本專欄的其餘部分一律將其簡稱為「過載」)是指允許使用者使用使用者定義的型別編寫表示式的能力。它允許使用者定義的型別與預定義的型別具有相同的功能。

例如,通常需要編寫類似於以下內容的**,以將兩個數字相加。很明顯,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# 允許過載的運算子很少。有兩條限制。首先,成員訪問、成員呼叫(也就是函式呼叫)、賦值以及「新建」無法過載,因為這些運算是執行時定義的。

其次,諸如「&&」、「||」、「?:」這樣的運算子以及諸如「+=」這樣的復合賦值運算子無法過載,因為這會使**變得異常複雜,得不償失。

讓我們返回到最初的示例:

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這樣的名稱進行命名。

本月只提供了乙個站點:

運算子過載之過載型別運算子

普通型別 類型別 呼叫對應的只有乙個引數 引數的型別就是這個普通型別 的建構函式 需求 boy boy1 10000 薪資 建構函式boy int boy boy2 rock 姓名 建構函式boy char 普通型別賦值給類型別其實很簡單,就是專門的對這個賦值的型別定義乙個建構函式。編譯器在執行 的...

運算子過載 賦值運算子的過載

有時候希望賦值運算子兩邊的型別可以不匹配,比如,把乙個int型別變數賦值給乙個complex物件,或把乙個 char 型別的字串賦值給乙個字串物件,此時就需要過載賦值運算子 注意 賦值運算子 只能過載為成員函式 賦值運算子過載例項示例 include include using namespace ...

運算子過載

c 中的運算子 1。大多數系統預定義運算子都能過載 不值得過載 不能被過載 2過載不能改變優先順序 不能改變結合性 不能改變運算子所需運算元的個數 過載後,可按這些運算子的表達方式使用 運算子過載的語法 一 通過運算子過載函式進行過載 1。運算子過載函式是成員函式 語法形式 type x opera...