Compartir vía


Sobrecarga del operador

En este tema se describe cómo sobrecargar operadores aritméticos en un tipo de clase o registro y en el nivel global.

Sintaxis

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

Observaciones

En la sintaxis anterior, el símbolo del operador es uno de +, -, */, , =, etc. La lista de parámetros especifica los operandos en el orden en que aparecen en la sintaxis habitual para ese operador. El cuerpo del método crea el valor resultante.

Las sobrecargas del operador para los operadores deben ser estáticas. Las sobrecargas de operador para operadores unarios, como + y -, deben usar una tilde (~) en el símbolo del operador para indicar que el operador es un operador unario y no un operador binario, como se muestra en la siguiente declaración.

static member (~-) (v : Vector)

En el código siguiente se muestra una clase vectorial que tiene solo dos operadores, uno para unario menos y otro para la multiplicación por un escalar. En el ejemplo, se necesitan dos sobrecargas para la multiplicación escalar porque el operador debe funcionar independientemente del orden en que aparezca el vector y el escalar.

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

Salida:

1 2
2 4
2 4
-2 -4

Creación de nuevos operadores

Puede sobrecargar todos los operadores estándar, pero también puede crear operadores nuevos fuera de secuencias de determinados caracteres. Los caracteres de operador permitidos son !, $, %, &, *+-./<=>?@^|, y .~ El ~ carácter tiene el significado especial de hacer un operador unario y no forma parte de la secuencia de caracteres del operador. No todos los operadores se pueden hacer unarios.

Dependiendo de la secuencia de caracteres exacta que use, el operador tendrá una prioridad y asociatividad determinada. La asociatividad puede ser de izquierda a derecha o derecha a izquierda y se usa cada vez que los operadores del mismo nivel de precedencia aparecen en secuencia sin paréntesis.

El carácter . de operador no afecta a la prioridad, por lo que, por ejemplo, si desea definir su propia versión de multiplicación que tenga la misma precedencia y asociatividad que la multiplicación normal, podría crear operadores como .*.

El $ operador debe estar independiente y sin símbolos adicionales.

Una tabla que muestra la prioridad de todos los operadores de F# se puede encontrar en Referencia de símbolos y operadores.

Nombres de operador sobrecargados

Cuando el compilador de F# compila una expresión de operador, genera un método que tiene un nombre generado por el compilador para ese operador. Este es el nombre que aparece en el lenguaje intermedio común (CIL) para el método y también en reflexión e IntelliSense. Normalmente no es necesario usar estos nombres en el código de F#.

En la tabla siguiente se muestran los operadores estándar y sus nombres generados correspondientes.

Operador Nombre generado
[] 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

Tenga en cuenta que el not operador de F# no emite op_Inequality porque no es un operador simbólico. Es una función que emite IL que niega una expresión booleana.

Otras combinaciones de caracteres de operador que no aparecen aquí se pueden usar como operadores y tienen nombres que se componen mediante la concatenación de nombres para los caracteres individuales de la tabla siguiente. Por ejemplo, +! se convierte en op_PlusBang.

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

El uso de : en operadores personalizados está parcialmente reservado. Solo se puede usar en operadores donde el primer carácter es > o . donde el primer carácter después de cualquier número de iniciales . es > , por ejemplo, >: o .>:.

Operadores de prefijo e infijo

Se espera que los operadores de prefijo se coloquen delante de un operando o operando, de forma muy similar a una función. Se espera que los operadores de infijo se coloquen entre los dos operandos.

Solo se pueden usar determinados operadores como operadores de prefijo. Algunos operadores son siempre operadores de prefijo, otros pueden ser infijos o prefijos, y el resto siempre son operadores infijos. Los operadores que comienzan por !, excepto !=, y el operador ~, o secuencias repetidas de ~, siempre son operadores de prefijo. Los operadores +, -, , +.-., &, &&, %, y %% pueden ser operadores de prefijo o operadores de infijo. Para distinguir la versión de prefijo de estos operadores de la versión de infijo, agregue un ~ al principio de un operador de prefijo cuando se defina. No ~ se usa cuando se usa el operador , solo cuando se define.

Ejemplo

En el código siguiente se muestra el uso de sobrecargas de operadores para implementar un tipo de fracción. Una fracción se representa mediante un numerador y un denominador. La función hcf se usa para determinar el factor común más alto, que se usa para reducir las fracciones.

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

Salida:

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 en el nivel global

También puede definir operadores en el nivel global. El código siguiente define un operador +?.

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

La salida del código anterior es 12.

Puede redefinir los operadores aritméticos normales de esta manera, ya que las reglas de ámbito de F# dictan que los operadores recién definidos tienen prioridad sobre los operadores integrados.

La palabra clave inline se usa a menudo con operadores globales, que a menudo son funciones pequeñas que se integran mejor en el código de llamada. La creación de funciones de operador insertadas también les permite trabajar con parámetros de tipo resueltos estáticamente para generar código genérico resuelto estáticamente. Para obtener más información, vea Funciones insertadas y parámetros de tipo resueltos estáticamente.

Consulte también