Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym temacie opisano sposób przeciążenia operatorów arytmetycznych w klasie lub typie rekordu oraz na poziomie globalnym.
Składnia
// 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
Uwagi
W poprzedniej składni symbol-operatora jest jednym z +, -, *, /, , =i tak dalej. Lista parametrów określa operandy w kolejności, w której są wyświetlane w zwykłej składni dla tego operatora.
Treść metody konstruuje wynikową wartość.
Przeciążenia operatorów dla operatorów muszą być statyczne. Przeciążenia operatorów jednoargumentowych, takich jak + i -, muszą używać tyldy (~) w symbolu operatora , aby wskazać, że operator jest operatorem jednoargumentowym, a nie operatorem binarnym, jak pokazano w poniższej deklaracji.
static member (~-) (v : Vector)
Poniższy kod ilustruje klasę wektorów, która ma tylko dwa operatory, jeden dla jednoargumentowego minus i jeden do mnożenia przez skalarny. W tym przykładzie potrzebne są dwa przeciążenia mnożenia skalarnego, ponieważ operator musi działać niezależnie od kolejności, w jakiej pojawia się wektor i skalarny.
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())
Wyjście:
1 2
2 4
2 4
-2 -4
Tworzenie nowych operatorów
Można przeciążyć wszystkie standardowe operatory, ale można również tworzyć nowe operatory poza sekwencjami niektórych znaków. Dozwolone znaki operatora to !, $%&*+-./<=>?@^|i .~ Znak ~ ma specjalne znaczenie tworzenia jednoargumentowego operatora i nie jest częścią sekwencji znaków operatora. Nie wszystkie operatory mogą być jednoargumentowe.
W zależności od dokładnej używanej sekwencji znaków operator będzie miał określony pierwszeństwo i kojarzenie. Kojarzenie może być od lewej do prawej lub od prawej do lewej i jest używane, gdy operatory tego samego poziomu pierwszeństwa pojawiają się w sekwencji bez nawiasów.
Znak . operatora nie ma wpływu na pierwszeństwo, więc na przykład jeśli chcesz zdefiniować własną wersję mnożenia, która ma taki sam pierwszeństwo i skojarzenie jak zwykłe mnożenie, można utworzyć operatory takie jak .*.
Operator $ musi być autonomiczny i bez dodatkowych symboli.
Tabela przedstawiająca pierwszeństwo wszystkich operatorów w języku F# można znaleźć w temacie Symbol i Odwołanie operatora.
Przeciążone nazwy operatorów
Gdy kompilator języka F# kompiluje wyrażenie operatora, generuje metodę, która ma nazwę wygenerowaną przez kompilator dla tego operatora. Jest to nazwa wyświetlana w typowym języku pośrednim (CIL) dla metody, a także w odbiciu i funkcji IntelliSense. Zwykle nie trzeba używać tych nazw w kodzie języka F#.
W poniższej tabeli przedstawiono standardowe operatory i odpowiadające im nazwy wygenerowane.
| Obsługujący | Wygenerowana nazwa |
|---|---|
[] |
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 |
Należy pamiętać, że not operator w języku F# nie emituje op_Inequality , ponieważ nie jest operatorem symbolicznym. Jest to funkcja, która emituje IL, która neguje wyrażenie logiczne.
Inne kombinacje znaków operatora, które nie są wymienione tutaj, mogą być używane jako operatory i mają nazwy tworzone przez łączenie nazw poszczególnych znaków z poniższej tabeli. Na przykład, +! staje się op_PlusBang.
| Znak operatora | Nazwa |
|---|---|
> |
Greater |
< |
Less |
+ |
Plus |
- |
Minus |
* |
Multiply |
/ |
Divide |
= |
Equals |
~ |
Twiddle |
$ |
Dollar |
% |
Percent |
. |
Dot |
& |
Amp |
| |
Bar |
@ |
At |
^ |
Hat |
! |
Bang |
? |
Qmark |
( |
LParen |
, |
Comma |
) |
RParen |
[ |
LBrack |
] |
RBrack |
: |
Colon |
Użycie funkcji : w operatorach niestandardowych jest częściowo zarezerwowane. Może być używany tylko w operatorach, w których pierwszy znak jest > lub . gdzie pierwszy znak po dowolnej liczbie wiodącej . jest > np. >: lub .>:.
Operatory prefiksu i prefiksu
Oczekuje się, że operatory prefiksu zostaną umieszczone przed operandem lub operandami, podobnie jak funkcja. Operatory infiksu powinny zostać umieszczone między dwoma operandami.
Jako operatory prefiksu można używać tylko niektórych operatorów. Niektóre operatory są zawsze operatorami prefiksów, inne mogą mieć prefiks lub prefiks, a pozostałe są zawsze operatorami przyrostków. Operatory rozpoczynające się od !, z wyjątkiem !=, i operator ~, lub powtarzające się sekwencje , są zawsze operatorami prefiksów ~. Operatory +, , -+.-.&&&, %i %% mogą być operatorami prefiksów lub operatorami przyrostków. Należy odróżnić wersję prefiksu tych operatorów od wersji prefiksu, dodając element ~ na początku operatora prefiksu, gdy jest zdefiniowany. Parametr ~ nie jest używany podczas korzystania z operatora tylko wtedy, gdy jest zdefiniowany.
Przykład
Poniższy kod ilustruje użycie przeciążenia operatora w celu zaimplementowania typu ułamkowego. Ułamek jest reprezentowany przez licznik i mianownik. Funkcja hcf służy do określania najwyższego wspólnego współczynnika, który jest używany do zmniejszenia ułamków.
// 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())
Wyjście:
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
Operatory na poziomie globalnym
Operatory można również definiować na poziomie globalnym. Poniższy kod definiuje operator +?.
let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)
Dane wyjściowe powyższego kodu to 12.
W ten sposób można ponownie zdefiniować zwykłe operatory arytmetyczne, ponieważ reguły określania zakresu dla języka F# określają, że nowo zdefiniowane operatory mają pierwszeństwo przed wbudowanymi operatorami.
Słowo kluczowe inline jest często używane z operatorami globalnymi, które są często małymi funkcjami, które najlepiej integrują się z kodem wywołującym. Tworzenie wbudowanych funkcji operatorów umożliwia również pracę z statycznie rozwiązanymi parametrami typu w celu utworzenia statycznie rozpoznanego kodu ogólnego. Aby uzyskać więcej informacji, zobacz Funkcje wbudowane i statycznie rozwiązane parametry typu.