运算符重载
本主题介绍如何在类或记录类型中,以及在全局级别下重载算术运算符。
语法
// 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# 编译器编译运算符表达式时会生成一个方法,该方法具有编译器为该运算符生成的名称。 这是公共中间语言 (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 |
请注意,F# 中的 not
运算符不会触发 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
通常与全局运算符结合使用,这些全局运算符通常是最适合集成到调用代码中的小函数。 如果使运算符函数内联,则还可以将其与静态解析的类型参数结合使用,以生成静态解析的泛型代码。 有关详细信息,请参阅内联函数和静态解析的类型参数。