運算子多載 (F#)
本主題描述如何在類別或記錄型別以及全域層級多載算術運算子。
// Overloading an operator as a class or record member.
static member (operator-symbols) (parameter-list) =
method-body
// Overloading an operator at the global level
let [inline] (operator-symbols) parameter-list =
function-body
備註
在先前的語法中,operator-symbol 是 +、-、*、/、= 等其中一個項目。parameter-list 會依運算元出現在該運算子一般語法中的順序來指定運算元。method-body 會建構結果值。
運算子的運算子多載必須是靜態。一元運算子 (例如 + 和 -) 的運算子多載必須在 operator-symbol 中使用波狀符號 (~),表示運算子為一元運算子而不是二元運算子,如下列宣告所示。
static member (~-) (v : Vector)
下列程式碼說明只有兩個運算子的向量類別,一個運算子用於一元負運算,一個運算子用於純量乘法。在範例中,純量乘法需要兩個多載,因為無論向量和純量出現的順序為何,運算子都必須執行。
type Vector(x: float, y : float) =
member this.x = x
member this.y = y
static member (~-) (v : Vector) =
Vector(-1.0 * v.x, -1.0 * v.y)
static member (*) (v : Vector, a) =
Vector(a * v.x, a * v.y)
static member (*) (a, v: Vector) =
Vector(a * v.x, a * v.y)
override this.ToString() =
this.x.ToString() + " " + this.y.ToString()
let v1 = Vector(1.0, 2.0)
let v2 = v1 * 2.0
let v3 = 2.0 * v1
let v4 = - v2
printfn "%s" (v1.ToString())
printfn "%s" (v2.ToString())
printfn "%s" (v3.ToString())
printfn "%s" (v4.ToString())
建立新運算子
您可以多載所有標準運算子,但也可以從某些字元序列中建立新運算子。允許的運算子字元為 !、%、&、*、+、-、.、/、<、=、>、?、@、^、| 和 ~。~ 字元具有讓運算子成為一元運算子的特殊意義,不是運算子字元序列的一部分。不是所有的運算子都可以是一元運算子,如本主題稍後的前置運算子和後置運算子中所述。
根據使用的確切字元序列,運算子會有特定的優先順序和順序關聯性。順序關聯性可以是從左到右或從右到左,每當序列中出現不含括號、相同優先順序等級的運算子時便會使用關聯性。
運算子字元 . 不會影響優先順序,因此如果您要定義與一般乘法有相同優先順序和順序關聯性的專屬乘法版本,可以建立運算子如 .*。
符號和運算子參考 (F#)中的表格會說明所有 F# 運算子的優先順序。
多載運算子名稱
當 F# 編譯器編譯運算子運算式時會產生方法,其具有編譯器為該運算子所產生的名稱。這個名稱就是以 Microsoft Intermediate Language (MSIL) 表示的方法名稱,也是反映和 IntelliSense 中的方法名稱。您通常不需要在 F# 程式碼中使用這些名稱。
下表將顯示標準運算子及產生的對應名稱。
運算子 |
產生的名稱 |
---|---|
[] |
op_Nil |
:: |
op_Cons |
+ |
op_Addition |
- |
op_Subtraction |
* |
op_Multiply |
/ |
op_Division |
@ |
op_Append |
^ |
op_Concatenate |
% |
op_Modulus |
&&& |
op_BitwiseAnd |
||| |
op_BitwiseOr |
^^^ |
op_ExclusiveOr |
<<< |
op_LeftShift |
~~~ |
op_LogicalNot |
>>> |
op_RightShift |
~+ |
op_UnaryPlus |
~- |
op_UnaryNegation |
= |
op_Equality |
<= |
op_LessThanOrEqual |
>= |
op_GreaterThanOrEqual |
< |
op_LessThan |
> |
op_GreaterThan |
? |
op_Dynamic |
?<- |
op_DynamicAssignment |
|> |
op_PipeRight |
<| |
op_PipeLeft |
! |
op_Dereference |
>> |
op_ComposeRight |
<< |
op_ComposeLeft |
<@ @> |
op_Quotation |
<@@ @@> |
op_QuotationUntyped |
+= |
op_AdditionAssignment |
-= |
op_SubtractionAssignment |
*= |
op_MultiplyAssignment |
/= |
op_DivisionAssignment |
.. |
op_Range |
.. .. |
op_RangeStep |
未列在此處的其他運算子字元組合也可以做為運算子,而其名稱可為下表中的個別字元名稱串連組成。例如,+! 變成 op_PlusBang。
運算子字元 |
名稱 |
---|---|
> |
Greater |
< |
Less |
+ |
Plus |
- |
Minus |
* |
Multiply |
/ |
Divide |
= |
Equals |
~ |
Twiddle |
% |
Percent |
. |
Dot |
& |
Amp |
| |
Bar |
@ |
At |
^ |
Hat |
! |
Bang |
? |
Qmark |
( |
LParen |
, |
Comma |
) |
RParen |
[ |
LBrack |
] |
RBrack |
前置和中置運算子
Prefix 運算子預期是放在運算元的前面,與函式十分類似。Infix 運算子預期是放在兩個運算元之間。
只有某些運算子可以當成前置詞運算子。某些運算子一律是前置運算子,有些可以是中置或前置,其餘則一定是中置運算子。開頭的運算子!,除了!=,且運算子為~,或重複序列的~,一定是以前置運算子。運算子 +、-、+.、-.、&、&&、% 和 %% 可以是前置運算子或中置運算子。您可以在定義前置運算子時將 ~ 加至其開頭,以便區別這些運算子的前置版本和中置版本。使用運算子時不使用 ~,只有定義該運算子時才會使用。
範例
在下列程式碼中,會示範使用運算子多載來實作分數型別的方式。分數是由分子和分母表示。函式 hcf 用來判斷用於約分的最大公因數。
// Determine the highest common factor between
// two positive integers, a helper for reducing
// fractions.
let rec hcf a b =
if a = 0u then b
elif a<b then hcf a (b - a)
else hcf (a - b) b
// type Fraction: represents a positive fraction
// (positive rational number).
type Fraction =
{
// n: Numerator of fraction.
n : uint32
// d: Denominator of fraction.
d : uint32
}
// Produce a string representation. If the
// denominator is "1", do not display it.
override this.ToString() =
if (this.d = 1u)
then this.n.ToString()
else this.n.ToString() + "/" + this.d.ToString()
// Add two fractions.
static member (+) (f1 : Fraction, f2 : Fraction) =
let nTemp = f1.n * f2.d + f2.n * f1.d
let dTemp = f1.d * f2.d
let hcfTemp = hcf nTemp dTemp
{ n = nTemp / hcfTemp; d = dTemp / hcfTemp }
// Adds a fraction and a positive integer.
static member (+) (f1: Fraction, i : uint32) =
let nTemp = f1.n + i * f1.d
let dTemp = f1.d
let hcfTemp = hcf nTemp dTemp
{ n = nTemp / hcfTemp; d = dTemp / hcfTemp }
// Adds a positive integer and a fraction.
static member (+) (i : uint32, f2: Fraction) =
let nTemp = f2.n + i * f2.d
let dTemp = f2.d
let hcfTemp = hcf nTemp dTemp
{ n = nTemp / hcfTemp; d = dTemp / hcfTemp }
// Subtract one fraction from another.
static member (-) (f1 : Fraction, f2 : Fraction) =
if (f2.n * f1.d > f1.n * f2.d)
then failwith "This operation results in a negative number, which is not supported."
let nTemp = f1.n * f2.d - f2.n * f1.d
let dTemp = f1.d * f2.d
let hcfTemp = hcf nTemp dTemp
{ n = nTemp / hcfTemp; d = dTemp / hcfTemp }
// Multiply two fractions.
static member (*) (f1 : Fraction, f2 : Fraction) =
let nTemp = f1.n * f2.n
let dTemp = f1.d * f2.d
let hcfTemp = hcf nTemp dTemp
{ n = nTemp / hcfTemp; d = dTemp / hcfTemp }
// Divide two fractions.
static member (/) (f1 : Fraction, f2 : Fraction) =
let nTemp = f1.n * f2.d
let dTemp = f2.n * f1.d
let hcfTemp = hcf nTemp dTemp
{ n = nTemp / hcfTemp; d = dTemp / hcfTemp }
// A full set of operators can be quite lengthy. For example,
// consider operators that support other integral data types,
// with fractions, on the left side and the right side for each.
// Also consider implementing unary operators.
let fraction1 = { n = 3u; d = 4u }
let fraction2 = { n = 1u; d = 2u }
let result1 = fraction1 + fraction2
let result2 = fraction1 - fraction2
let result3 = fraction1 * fraction2
let result4 = fraction1 / fraction2
let result5 = fraction1 + 1u
printfn "%s + %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result1.ToString())
printfn "%s - %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result2.ToString())
printfn "%s * %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result3.ToString())
printfn "%s / %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result4.ToString())
printfn "%s + 1 = %s" (fraction1.ToString()) (result5.ToString())
全域層級的運算子
您也可以在全域層級定義運算子。下列程式碼會定義運算子 +?。
let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)
上述程式碼的輸出是 12。
您可以用這種方式重新定義一般算術運算子,因為 F# 的範圍規則表示新定義的運算子優先於內建的運算子。
關鍵字 inline 常搭配全域運算子使用,全域運算子通常是小型函式,最適合整合在呼叫程式碼中。使運算子函式成為內嵌函式,也可讓它們使用以靜態解析的型別參數,以產生以靜態解析的泛型程式碼。如需詳細資訊,請參閱內嵌函式 (F#)和以統計方式解析的型別參數 (F#)。