运算符重载 (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 中间语言 (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

前缀和中缀运算符

“前缀”运算符应放置在一个或多个操作数的前面,与函数非常类似。 “中缀”运算符应放置在两个操作数之间。

只有某些运算符可以用作前缀运算符。 某些运算符总是前缀运算符,其他一些可以中缀或前缀,而剩余的一些则始终是中缀运算符。 从 !启动的运算符,除此之外, !=和运算符 ~或~重复的序列,始终为前缀运算符。 运算符+、-、+.、-.、&、&&、% 和 %% 可以是前缀运算符,也可以是中缀运算符。 通过在定义前缀运算符时在其开头添加 ~ 将这些运算符的前缀版本与中缀版本区分开来。 在使用运算符时不使用~,仅在定义时用到。

示例

下面的代码演示如何使用运算符重载来实现分数类型。 分数由分子和分母表示。 函数 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#)

请参见

其他资源

成员 (F#)