Перегрузка операторов

В этом разделе описывается, как перегружать арифметические операторы в классе или типе записей и на глобальном уровне.

Синтаксис

// 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# компилирует выражение оператора, он создает метод, имеющий имя, созданное компилятором для этого оператора. Это имя, которое отображается в общем промежуточном языке (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# не выдаетop_Inequality, not так как это не символьный оператор. Это функция, которая выдает 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

Префикс и операторы Infix

Как ожидается, операторы префикса будут помещены перед операндом или операндом, как и функция. Ожидается, что операторы Infix будут размещены между двумя операндами.

В качестве операторов префикса можно использовать только определенные операторы. Некоторые операторы всегда являются операторами префикса, другие могут быть infix или префикс, а остальные всегда являются операторами 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 часто используется с глобальными операторами, которые часто являются небольшими функциями, которые лучше всего интегрированы в вызывающий код. Создание встроенных функций оператора также позволяет им работать со статически разрешенными параметрами типа для создания статического разрешенного универсального кода. Дополнительные сведения см. в разделе "Встроенные функции " и "Статически разрешенные параметры типа".

См. также