程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 幫幫我,我負載過重(C#)

幫幫我,我負載過重(C#)

編輯:關於C語言
作為有關 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)
{
  VIEwer.Add(row);
  row++;
}

IV
Account current = findAccount(idNum);
current += 5;

答案和討論
1
本示例中,要執行的運算是顯而易見的。這種加法只不過是將預定義的類型相加,每個人都明白執行了什麼運算,因此在
這個示例中,使用運算符重載很有意義。

B
本示例演示了矩陣如何相乘。從概念上來說,矩陣乘法與常規乘法不完全類似,但它是一個明確定義的運算,因此任何理
解矩陣乘法的人看到這種重載的運算符時,都不會感到驚訝。

iii
本示例中,增量 (++) 運算符進行了重載,它使數據庫行向前移至下一行。任何與數據庫行有關的事物都不可能使我們理
解這種增量的真正含義,而且,這種增量要執行的運算也不是那麼明顯。

在這一示例中,重載的使用也沒有使代碼變得更簡單。如果我們轉而使用以下代碼,情況就好多了:

DBRow row = query.Execute();
while (!row.MoveNext())
{
  VIEwer.Add(row);
}

IV
將事物和雇員相加代表什麼含義呢?本示例中,選擇是一個不錯的方法,將其與雇員數相加就會注冊雇員。這是一種很糟
糕的運算符重載用法。

原則
何時進行重載的原則是相當簡單的。如果用戶希望能執行這種運算,那麼就應該進行重載。

重載算術運算符
要重載 C# 中的運算符,指定要執行運算的函數就可以了。函數必須在運算所涉及的類型中進行定義,並且至少有一個參
數屬於該類型。這樣可以防止對 int 的加法或其它奇怪事物進行重載。

為了演示重載,我們將開發一個矢量。矢量可以被認為是從原點到特定二維點的線。可以對矢量執行多種運算。以下是該
類型的粗略定義:

struct Vector
{
  float x;
  float y;

  public Vector(float x, float y)
  {
      this.x = x;
      this.y = y;
  }
}

要實際使用,矢量應支持以下運算:

獲取長度
將矢量乘以某個數字
將矢量除以某個數字
將兩個矢量相加
將一個矢量減去另一個矢量
計算兩個矢量的點積
我們的任務是確定應該如何實現這些運算。

長度
對於獲取矢量的長度,似乎沒有任何有意義的運算符。長度不會變化,因此將它作為屬性是很有意義的:

  public float Length
  {
      get
      {
        return((float) Math.Sqrt(x * x + y * y));
      }
  }

將矢量乘以/除以某個數字
將矢量乘以某個數字是相當常見的運算,並且是用戶希望實現的運算。以下是相關代碼:

  public static Vector Operator*(Vector vector, float multiplIEr)
  {
      return(new Vector(vector.x * multiplIEr,
vector.y * multiplIEr));
  }

應該注意,此處有許多有趣的現象。首先,運算符是 static 函數,因此它必須獲取兩個參數的值,同時在結果中必須返
回一個新的對象。運算符的名稱恰好是“Operator”,後面緊跟著要重載的運算符。

除以某個數字的代碼與以上代碼類似。

將兩個矢量進行加減
這是很常見的矢量運算,因此很顯然要對它們進行重載。

  public static Vector Operator+(Vector vector1, Vector vector2)
  {
      return(new Vector(vector1.x + vector2.x,
vector1.y + vector2.y));
  }

減法的代碼與以上代碼非常類似。

計算點積
兩個矢量的點積是為矢量定義的特殊運算,在預定義的類型中根本無法找到與之相類似的運算。在方程式中,點積通過在
兩個矢量之間寫一個點來表示,因此它和任何現有運算符都不是精確匹配。點積的一個有趣特征是:它獲取兩個矢量的
值,但只返回一個簡單的數字。

無論是否對該運算進行重載,用戶代碼都大致相同。第一行顯示了正在使用的重載版本,其它行則顯示了兩個替代版本:

  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)
  {
      return(v1.x * v2.x + v1.y * v2.y);
  }

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)
  {
      return(new Complex(value, 1.0));
  }

這允許進行從 int 到 Complex 的隱式轉換。

語言的互操作性
以上是在 C# 中對運算符進行重載的情況。涉及到其它語言時,事情將變得略為復雜。

運算符重載不是 .Net 公共語言子集中的功能之一,這意味著在某些語言中將無法使用重載。因此,提供非重載的替代方
案是非常重要的,以便在其它語言中仍然能執行相同的運算。如果您的類定義了加法運算符,它還應該定義相同的方法,
使用類似 Add 這樣的名稱進行命名。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved