Partager via


Surcharge des opérateurs

Cette rubrique explique comment surcharger des opérateurs arithmétiques dans un type de classe ou d’enregistrement et au niveau global.

Syntaxe

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

Remarques

Dans la syntaxe précédente, l’opérateur-symbole est l’un des +éléments suivants : , -, */, =, et ainsi de suite. La liste des paramètres spécifie les opérandes dans l’ordre dans lequel ils apparaissent dans la syntaxe habituelle de cet opérateur. Le corps de la méthode construit la valeur résultante.

Les surcharges d’opérateur pour les opérateurs doivent être statiques. Les surcharges d’opérateur pour les opérateurs unaires, tels que + et -, doivent utiliser un tilde (~) dans le symbole d’opérateur pour indiquer que l’opérateur est un opérateur unaire et non un opérateur binaire, comme indiqué dans la déclaration suivante.

static member (~-) (v : Vector)

Le code suivant illustre une classe vectorielle qui n’a que deux opérateurs, un pour un moins unaire et un pour la multiplication par un scalaire. Dans l’exemple, deux surcharges pour la multiplication scalaire sont nécessaires, car l’opérateur doit fonctionner indépendamment de l’ordre dans lequel le vecteur et la scalaire apparaissent.

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

Output:

1 2
2 4
2 4
-2 -4

Création d’opérateurs

Vous pouvez surcharger tous les opérateurs standard, mais vous pouvez également créer de nouveaux opérateurs hors séquences de certains caractères. Les caractères d’opérateur autorisés sont !, $%&*+-./<=>?@^|et .~ Le ~ caractère a la signification spéciale de créer un opérateur unaire et ne fait pas partie de la séquence de caractères d’opérateur. Tous les opérateurs ne peuvent pas être rendus unaires.

Selon la séquence de caractères exacte que vous utilisez, votre opérateur aura une certaine priorité et une associativité. L’associativité peut être de gauche à droite ou de droite à gauche et est utilisée chaque fois que les opérateurs du même niveau de priorité apparaissent dans la séquence sans parenthèses.

Le caractère . d’opérateur n’affecte pas la précédence, de sorte que, par exemple, si vous souhaitez définir votre propre version de multiplication qui a la même précédence et l’associativité que la multiplication ordinaire, vous pouvez créer des opérateurs tels que .*.

L’opérateur $ doit être autonome et sans symboles supplémentaires.

Une table qui affiche la priorité de tous les opérateurs en F# est disponible dans Symbol and Operator Reference.

Noms d’opérateurs surchargés

Lorsque le compilateur F# compile une expression d’opérateur, il génère une méthode qui a un nom généré par le compilateur pour cet opérateur. Il s’agit du nom qui apparaît dans le langage intermédiaire commun (CIL) de la méthode, ainsi que dans la réflexion et IntelliSense. Vous n’avez normalement pas besoin d’utiliser ces noms dans le code F#.

Le tableau suivant présente les opérateurs standard et leurs noms générés correspondants.

Opérateur Nom généré
[] 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

Notez que l’opérateur not en F# n’émet op_Inequality pas, car il ne s’agit pas d’un opérateur symbolique. Il s’agit d’une fonction qui émet l’il qui annule une expression booléenne.

D’autres combinaisons de caractères d’opérateur qui ne sont pas répertoriés ici peuvent être utilisées en tant qu’opérateurs et ont des noms constitués par concaténation de noms pour les caractères individuels du tableau suivant. Par exemple, +! devient op_PlusBang.

Caractère d’opérateur Nom
> Greater
< Less
+ Plus
- Minus
* Multiply
/ Divide
= Equals
~ Twiddle
$ Dollar
% Percent
. Dot
& Amp
| Bar
@ At
^ Hat
! Bang
? Qmark
( LParen
, Comma
) RParen
[ LBrack
] RBrack
: Colon

L’utilisation des : opérateurs personnalisés est partiellement réservée. Il peut uniquement être utilisé dans les opérateurs où le premier caractère est > ou . où le premier caractère après un nombre quelconque de débuts . est > par exemple >: ou .>:.

Opérateurs préfixe et infix

Les opérateurs de préfixe sont censés être placés devant un opérande ou des opérandes, comme une fonction. Les opérateurs infixés sont censés être placés entre les deux opérandes.

Seuls certains opérateurs peuvent être utilisés comme opérateurs de préfixe. Certains opérateurs sont toujours des opérateurs de préfixe, d’autres peuvent être infixés ou préfixes, et les autres sont toujours des opérateurs infixés. Les opérateurs qui commencent par !, sauf !=, et l’opérateur ~, ou des séquences répétées de ~, sont toujours des opérateurs de préfixe. Les opérateurs +, , -, +.-., &&&, , , %et %% peuvent être des opérateurs de préfixe ou des opérateurs infix. Vous distinguez la version de préfixe de ces opérateurs de la version infix en ajoutant un ~ au début d’un opérateur de préfixe lorsqu’il est défini. Il ~ n’est pas utilisé lorsque vous utilisez l’opérateur, uniquement lorsqu’il est défini.

Exemple :

Le code suivant illustre l’utilisation de la surcharge d’opérateur pour implémenter un type de fraction. Une fraction est représentée par un numérateur et un dénominateur. La fonction hcf est utilisée pour déterminer le facteur commun le plus élevé, utilisé pour réduire les fractions.

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

Output:

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

Opérateurs au niveau global

Vous pouvez également définir des opérateurs au niveau global. Le code suivant définit un opérateur +?.

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

La sortie du code ci-dessus est 12.

Vous pouvez redéfinir les opérateurs arithmétiques standard de cette façon, car les règles d’étendue pour F# déterminent que les opérateurs nouvellement définis sont prioritaires sur les opérateurs intégrés.

Le mot clé inline est souvent utilisé avec des opérateurs globaux, qui sont souvent de petites fonctions qui sont mieux intégrées au code appelant. La création de fonctions d’opérateur inline permet également d’utiliser des paramètres de type résolus statiquement pour produire du code générique résolu statiquement. Pour plus d’informations, consultez Fonctions inline et paramètres de type résolus statiquement.

Voir aussi