程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> C#規范漫談

C#規范漫談

編輯:.NET實例教程

作為有關 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