共用方式為


運算子多載

本主題描述如何在類別或記錄類型中,以及在全域層級多載算術運算元。

語法

// 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

備註

在上一個語法中,運算符符號是 、、+-*/等的=其中一個。 parameter-list 會依照作數在運算符的一般語法中出現的順序來指定作數。 方法主體會建構產生的值。

運算子的運算子多載必須是靜態的。 一元運算子的運算符多載,例如 +-,必須使用~中的tilde ()來指出運算符是一元運算符,而不是二元運算元,如下列宣告所示。

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())

輸出:

1 2
2 4
2 4
-2 -4

建立新的運算符

您可以多載所有標準運算符,但您也可以從特定字元序列中建立新的運算符。 允許的運算符字元為!$、、、%&*+-./<=>?@^| 和 。~ 字元 ~ 具有讓運算符一元化的特殊意義,而且不是運算符字元序列的一部分。 並非所有運算子都可以一元進行。

根據您使用的確切字元序列,運算子會有特定優先順序和關聯性。 關聯性可以是由左至右或由右至左,而且每當相同優先順序層級的運算符以順序顯示時,不加括號即可使用。

運算符字元 . 不會影響優先順序,因此,例如,如果您想要定義自己的乘法版本,其優先順序和關聯性與一般乘法相同,您可以建立運算符,例如 .*

$運算子必須獨立且沒有其他符號。

您可以在 符號和運算子參考中找到 F# 中所有運算子的優先順序資料表。

多載運算子名稱

當 F# 編譯程式編譯運算子表示式時,它會產生方法,該運算元具有編譯程式產生的名稱。 這是方法的通用中繼語言 (CIL) 中顯示的名稱,也出現在反映和 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

請注意, not F# 中的運算子不會發出 op_Inequality ,因為它不是符號運算符。 它是發出 IL 的函式,會否定布爾運算式。

此處未列出的其他運算符字元組合可以做為運算符,並具有由下清單格中個別字串連名稱所組成的名稱。 例如,+! 會成為 op_PlusBang

運算符字元 名稱
> Greater
< Less
+ Plus
- Minus
* Multiply
/ Divide
= Equals
~ Twiddle
$ Dollar
% Percent
. Dot
& Amp
| Bar
@ At
^ Hat
! Bang
? Qmark
( LParen
, Comma
) RParen
[ LBrack
] RBrack
: Colon

在自訂運算子中使用是 : 部分保留的。 它只能用於第一個字元為 >. 任意數量行導 . 後的第一個字元為 > 例如 >:.>:的運算子。

前置詞和 Infix 運算符

前置 詞運算子應該放在作數或作數前面,這很像函式。 Infix 運算子應該放在兩個作數之間。

只有特定運算符可以做為前置詞運算符。 有些運算元一律為前置詞運算符,其他運算符可以是 infix 或 prefix,其餘運算元一律為 infix 運算子。 開頭為 !、、、 !=和運算子 的運算符 ~,或的重複序列 ~,一律是前置運算符。 運算子 +-+.、、、-.&&&%%% 可以是前置詞運算符或 infix 運算子。 您可以在定義前置詞運算符時,於前置詞運算符開頭新增 , ~ 以區分這些運算符的前置詞版本與 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())

輸出:

3/4 + 1/2 = 5/4
3/4 - 1/2 = 1/4
3/4 * 1/2 = 3/8
3/4 / 1/2 = 3/2
3/4 + 1 = 7/4

全域層級的運算符

您也可以在全域層級定義運算子。 下列程式代碼會定義 運算子 +?

let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)

此程式代碼輸出為 12

您可以以此方式重新定義一般算術運算子,因為 F# 的範圍規則規定新定義的運算符優先於內建運算元。

關鍵詞 inline 通常與全域運算元搭配使用,這些運算符通常是最能整合到呼叫程式代碼中的小型函式。 讓運算子函式內嵌也能夠使用靜態解析的類型參數,以產生靜態解析的泛型程序代碼。 如需詳細資訊,請參閱 內嵌函 式和 靜態解析型別參數

另請參閱