Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questo argomento viene descritto come eseguire l'overload degli operatori aritmetici in una classe o in un tipo di record e a livello globale.
Sintassi
// 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
Osservazioni:
Nella sintassi precedente, il simbolo dell'operatore è uno di +, -, *, /, =e così via.
L'elenco di parametri specifica gli operandi nell'ordine in cui vengono visualizzati nella sintassi consueta per tale operatore. Il corpo del metodo costruisce il valore risultante.
Gli overload degli operatori per gli operatori devono essere statici. Gli overload degli operatori unari, ad esempio + e -, devono usare una tilde (~) nel simbolo dell'operatore per indicare che l'operatore è un operatore unario e non un operatore binario, come illustrato nella dichiarazione seguente.
static member (~-) (v : Vector)
Il codice seguente illustra una classe vettore che ha solo due operatori, uno per unario meno e uno per la moltiplicazione per scalare. Nell'esempio sono necessari due overload per la moltiplicazione scalare perché l'operatore deve funzionare indipendentemente dall'ordine in cui vengono visualizzati il vettore e lo scalare.
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
Creazione di nuovi operatori
È possibile eseguire l'overload di tutti gli operatori standard, ma è anche possibile creare nuovi operatori da sequenze di determinati caratteri. I caratteri di operatore consentiti sono , , !, , $%, , &*+-./<=>?e .@^|~ Il ~ carattere ha il significato speciale di creare un operatore unario e non fa parte della sequenza di caratteri dell'operatore. Non tutti gli operatori possono essere resi unari.
A seconda della sequenza di caratteri esatta usata, l'operatore avrà una certa precedenza e associatività. L'associatività può essere da sinistra a destra o da destra a sinistra e viene usata ogni volta che gli operatori dello stesso livello di precedenza vengono visualizzati in sequenza senza parentesi.
Il carattere . dell'operatore non influisce sulla precedenza, in modo che, ad esempio, se si vuole definire la propria versione di moltiplicazione con la stessa precedenza e associatività della moltiplicazione ordinaria, è possibile creare operatori come .*.
L'operatore $ deve essere autonomo e senza simboli aggiuntivi.
Una tabella che mostra la precedenza di tutti gli operatori in F# è reperibile in Riferimento a simboli e operatori.
Nomi degli operatori di overload
Quando il compilatore F# compila un'espressione di operatore, genera un metodo con un nome generato dal compilatore per tale operatore. Questo è il nome visualizzato nel linguaggio intermedio comune (CIL) per il metodo e anche in reflection e IntelliSense. In genere non è necessario usare questi nomi nel codice F#.
La tabella seguente illustra gli operatori standard e i nomi generati corrispondenti.
| Operatore | Nome generato |
|---|---|
[] |
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 |
Si noti che l'operatore not in F# non genera op_Inequality perché non è un operatore simbolico. Si tratta di una funzione che genera IL che nega un'espressione booleana.
Altre combinazioni di caratteri dell'operatore non elencate qui possono essere usate come operatori e hanno nomi costituiti dalla concatenazione dei nomi per i singoli caratteri della tabella seguente. Ad esempio, +! diventa op_PlusBang.
| Carattere operatore | Nome |
|---|---|
> |
Greater |
< |
Less |
+ |
Plus |
- |
Minus |
* |
Multiply |
/ |
Divide |
= |
Equals |
~ |
Twiddle |
$ |
Dollar |
% |
Percent |
. |
Dot |
& |
Amp |
| |
Bar |
@ |
At |
^ |
Hat |
! |
Bang |
? |
Qmark |
( |
LParen |
, |
Comma |
) |
RParen |
[ |
LBrack |
] |
RBrack |
: |
Colon |
L'uso di : negli operatori personalizzati è parzialmente riservato. Può essere usato solo negli operatori in cui il primo carattere è > o . dove il primo carattere dopo un numero qualsiasi di caratteri iniziali . è > , ad esempio >: o .>:.
Operatori prefisso e infix
È previsto che gli operatori di prefisso vengano posizionati davanti a un operando o operando, in modo analogo a una funzione. Gli operatori di prefisso devono essere posizionati tra i due operandi.
Solo alcuni operatori possono essere usati come operatori di prefisso. Alcuni operatori sono sempre operatori di prefisso, altri possono essere infissi o prefissi e gli altri sono sempre operatori di prefisso. Gli operatori che iniziano con !, ad eccezione !=di e , e l'operatore ~, o sequenze ripetute di ~, sono sempre operatori di prefisso. Gli operatori +, , -+.-., &&&, , %, e %% possono essere operatori di prefisso o operatori di prefisso. È possibile distinguere la versione del prefisso di questi operatori dalla versione infix aggiungendo un oggetto ~ all'inizio di un operatore prefisso quando viene definito. Non ~ viene utilizzato quando si usa l'operatore , solo quando viene definito.
Esempio
Il codice seguente illustra l'uso dell'overload degli operatori per implementare un tipo frazionario. Una frazione è rappresentata da un numeratore e un denominatore. La funzione hcf viene usata per determinare il fattore comune più elevato, usato per ridurre le frazioni.
// 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
Operatori a livello globale
È anche possibile definire operatori a livello globale. Il codice seguente definisce un operatore +?.
let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)
L'output del codice precedente è 12.
È possibile ridefinire gli operatori aritmetici regolari in questo modo perché le regole di ambito per F# determinano che gli operatori appena definiti hanno la precedenza sugli operatori predefiniti.
La parola chiave inline viene spesso usata con operatori globali, che spesso sono funzioni di piccole dimensioni che sono meglio integrate nel codice chiamante. La creazione di funzioni dell'operatore inline consente anche di lavorare con parametri di tipo risolti in modo statico per produrre codice generico risolto in modo statico. Per altre informazioni, vedere Funzioni inline e Parametri di tipo risolti in modo statico.