运算符重载

本主题介绍如何在类或记录类型以及全局级别重载算术运算符。

语法

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

备注

在前面的语法中, 运算符符号+ 、、、、等之一 - * / =参数列表 按操作数在该运算符的常用语法中出现的顺序指定它们。 方法体 构造生成的值。

运算符的运算符重载必须是静态的。 一元运算符(如和)的运算符 + 重载 - 必须使用 ~ 运算符符号 中的波形符 () ,以指示该运算符是一元运算符而不是二元运算符,如下面的声明所示。

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 # 编译器编译运算符表达式时,它将生成一个方法,该方法具有该运算符的编译器生成的名称。 这是在 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

请注意, 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

前缀和中缀运算符

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

只有某些运算符可以用作前缀运算符。 一些运算符始终是前缀运算符,其他运算符可以是中缀或前缀,其余运算符始终是中缀运算符。 除了 ! 之外,以 != 开头的运算符以及运算符 ~~ 的重复序列始终为前缀运算符。 运算符、、、、、、 + - +. -. & && %%% 可以是前缀运算符或中缀运算符。 在定义时,可以通过 ~ 在前缀运算符开头添加来区分这些运算符的前缀版本。 ~仅当定义了运算符时,才使用。

示例

下面的代码演示如何使用运算符重载来实现分数类型。 分数由分子和分母表示。 函数 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 通常与全局运算符一起使用,全局运算符通常是最能集成到调用代码中的小函数。 通过使 operator 函数成为内联函数,它们可以使用静态解析的类型参数来生成静态解析的泛型代码。 有关详细信息,请参阅 内联函数静态解析的类型参数

另请参阅