Partilhar via


Sobrecarga do operador

Este tópico descreve como sobrecarregar operadores aritméticos em uma classe ou tipo de registro e em nível global.

Sintaxe

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

Observações

Na sintaxe anterior, o símbolo-operador é um de +, -, *, , /, =e assim por diante. A lista de parâmetros especifica os operandos na ordem em que aparecem na sintaxe usual para esse operador. O corpo-método constrói o valor resultante.

As sobrecargas dos operadores devem ser estáticas. As sobrecargas de operador para operadores unários, como + e -, devem usar um til (~) no símbolo do operador para indicar que o operador é um operador unário e não um operador binário, como mostrado na declaração a seguir.

static member (~-) (v : Vector)

O código a seguir ilustra uma classe de vetor que tem apenas dois operadores, um para unário menos e outro para multiplicação por um escalar. No exemplo, duas sobrecargas para multiplicação escalar são necessárias porque o operador deve trabalhar independentemente da ordem em que o vetor e o escalar aparecem.

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

Criação de novos operadores

Você pode sobrecarregar todos os operadores padrão, mas também pode criar novos operadores a partir de sequências de determinados caracteres. Os caracteres de operador permitidos são !, , , %, -^&*.+/?|<=>@e .~$ O ~ caractere tem o significado especial de tornar um operador unário, e não faz parte da sequência de caracteres do operador. Nem todos os operadores podem ser tornados unários.

Dependendo da sequência exata de caracteres que você usar, seu operador terá uma certa precedência e associatividade. A associatividade pode ser da esquerda para a direita ou da direita para a esquerda e é usada sempre que operadores do mesmo nível de precedência aparecem em sequência sem parênteses.

O caractere . operador não afeta a precedência, de modo que, por exemplo, se você quiser definir sua própria versão de multiplicação que tenha a mesma precedência e associatividade que a multiplicação ordinária, você pode criar operadores como .*.

O $ operador deve manter-se sozinho e sem símbolos adicionais.

Uma tabela que mostra a precedência de todos os operadores em F# pode ser encontrada em Symbol and Operator Reference.

Nomes de operadores sobrecarregados

Quando o compilador F# compila uma expressão de operador, ele gera um método que tem um nome gerado pelo compilador para esse operador. Este é o nome que aparece na linguagem intermediária comum (CIL) para o método, e também em reflexão e IntelliSense. Normalmente, você não precisa usar esses nomes no código F#.

A tabela a seguir mostra os operadores padrão e seus nomes gerados correspondentes.

Operador Nome gerado
[] 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

Observe que o not operador em F# não emite op_Inequality porque não é um operador simbólico. É uma função que emite IL que nega uma expressão booleana.

Outras combinações de caracteres de operador que não estão listadas aqui podem ser usadas como operadores e têm nomes que são compostos por nomes concatenantes para os caracteres individuais da tabela a seguir. Por exemplo, +! torna-se op_PlusBang.

Caráter do operador Nome
> Greater
< Less
+ Plus
- Minus
* Multiply
/ Divide
= Equals
~ Twiddle
$ Dollar
% Percent
. Dot
& Amp
| Bar
@ At
^ Hat
! Bang
? Qmark
( LParen
, Comma
) RParen
[ LBrack
] RBrack

Operadores de prefixo e Infix

Espera-se que os operadores de prefixo sejam colocados na frente de um operando ou operandos, muito parecido com uma função. Espera-se que os operadores de infix sejam colocados entre os dois operandos.

Apenas determinados operadores podem ser usados como operadores de prefixo. Alguns operadores são sempre operadores de prefixo, outros podem ser infix ou prefixo, e o resto são sempre operadores infix. Os operadores que começam com !, exceto !=, e o operador ~, ou sequências repetidas de, são sempre operadores de~ prefixo. Os operadores +, -, , +., &-., &&, , %e %% podem ser operadores de prefixo ou operadores infix. Você distingue a versão de prefixo desses operadores da versão infix adicionando um ~ no início de um operador de prefixo quando ele é definido. O ~ não é usado quando você usa o operador, apenas quando ele é definido.

Exemplo

O código a seguir ilustra o uso da sobrecarga do operador para implementar um tipo de fração. Uma fração é representada por um numerador e um denominador. A função hcf é usada para determinar o maior fator comum, que é usado para reduzir frações.

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

Saída:

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

Operadores a nível global

Você também pode definir operadores em nível global. O código a seguir define um operador +?.

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

A saída do código acima é 12.

Você pode redefinir os operadores aritméticos regulares dessa maneira porque as regras de escopo para F# determinam que os operadores recém-definidos tenham precedência sobre os operadores internos.

A palavra-chave inline é frequentemente usada com operadores globais, que muitas vezes são pequenas funções que são melhor integradas no código de chamada. Tornar as funções do operador em linha também permite que eles trabalhem com parâmetros de tipo resolvidos estaticamente para produzir código genérico resolvido estaticamente. Para obter mais informações, consulte Funções embutidas e parâmetros de tipo resolvidos estaticamente.

Consulte também