연산자 오버로드
이 항목에서는 클래스 또는 레코드 종류와 전역 수준에서 산술 연산자를 오버로드하는 방법에 대해 설명합니다.
구문
// 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
설명
이전 구문에서 연산자 기호는 +
, -
, *
, /
, =
등 중 하나입니다. 매개 변수 목록은 해당 연산자의 일반적인 구문에 나타나는 순서대로 피연산자를 지정합니다. method-body는 결과 값을 구성합니다.
연산자에 대한 연산자 오버로드는 정적이어야 합니다. +
및 -
와 같은 단항 연산자에 대한 연산자 오버로드는 operator-symbol에 물결표(~
)를 사용하여 다음 선언과 같이 연산자가 이항 연산자가 아니라 단항 연산자임을 나타내야 합니다.
static member (~-) (v : Vector)
다음 코드에서는 연산자가 두 개뿐인 벡터 클래스를 보여 줍니다. 그 중 하나는 단항 빼기 연산자이고 다른 하나는 스칼라 값을 곱하기 위한 연산자입니다. 이 예에서는 연산자가 벡터와 스칼라가 나타나는 순서에 관계없이 작동해야 하기 때문에 스칼라 곱셈을 위한 두 개의 오버로드가 필요합니다.
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())
새로운 연산자 만들기
모든 표준 연산자를 오버로드할 수 있지만 특정 문자 시퀀스로 새 연산자를 만들 수도 있습니다. 허용되는 연산자 문자는 !
, $
, %
, &
, *
, +
, -
, .
, /
, <
, =
, >
, ?
, @
, ^
, |
및 ~
입니다. ~
문자는 연산자를 단항으로 만드는 특별한 의미를 가지며 연산자 문자 시퀀스의 일부가 아닙니다. 모든 연산자를 단항으로 만들 수 있는 것은 아닙니다.
사용하는 정확한 문자 시퀀스에 따라 연산자의 우선 순위와 연관성이 달라집니다. 연관성은 왼쪽에서 오른쪽 또는 오른쪽에서 왼쪽일 수 있으며 동일한 우선 순위 수준의 연산자가 괄호 없이 순서대로 나타날 때마다 사용됩니다.
연산자 문자 .
은 우선 순위에 영향을 주지 않으므로, 예를 들어 일반 곱셈과 동일한 우선 순위 및 연관성을 갖는 고유한 곱셈 버전을 정의하려는 경우 .*
와 같은 연산자를 만들 수 있습니다.
$
연산자는 추가 기호 없이 단독으로 사용해야 합니다.
F#의 모든 연산자의 우선 순위를 보여 주는 표는 기호 및 연산자 참조에서 찾을 수 있습니다.
오버로드된 연산자 이름
F# 컴파일러는 연산자 식을 컴파일할 때 해당 연산자에 대해 컴파일러에서 생성된 이름이 있는 메서드를 생성합니다. 이는 메서드의 CIL(공용 중간 언어)과 리플렉션 및 IntelliSense에 나타나는 이름입니다. 일반적으로 F# 코드에서는 이러한 이름을 사용할 필요가 없습니다.
다음 표에는 표준 연산자와 해당 생성 이름이 나와 있습니다.
연산자 | 생성된 이름 |
---|---|
[] |
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 |
F#의 not
연산자는 기호 연산자가 아니기 때문에 op_Inequality
를 내보내지 않습니다. 부울 식을 부정하는 IL을 내보내는 함수입니다.
여기에 나열되지 않은 다른 연산자 문자 조합은 연산자로 사용될 수 있으며 다음 표의 개별 문자에 대한 이름을 연결하여 구성되는 이름을 갖습니다. 예를 들어, +!는 op_PlusBang
이 됩니다.
연산자 문자 | 속성 |
---|---|
> |
Greater |
< |
Less |
+ |
Plus |
- |
Minus |
* |
Multiply |
/ |
Divide |
= |
Equals |
~ |
Twiddle |
$ |
Dollar |
% |
Percent |
. |
Dot |
& |
Amp |
| |
Bar |
@ |
At |
^ |
Hat |
! |
Bang |
? |
Qmark |
( |
LParen |
, |
Comma |
) |
RParen |
[ |
LBrack |
] |
RBrack |
전위 연산자 및 중위 연산자
전위 연산자는 함수와 마찬가지로 피연산자 앞에 배치되어야 합니다. 중위 연산자는 두 피연산자 사이에 배치되어야 합니다.
특정 연산자만 전위 연산자로 사용할 수 있습니다. 일부 연산자는 항상 전위 연산자이고, 다른 연산자는 중위 또는 전위일 수 있으며, 나머지는 항상 중위 연산자입니다. !
를 제외한 !=
로 시작하는 연산자와 ~
연산자 또는 ~
연산자의 반복적인 시퀀스는 항상 전위 연산자입니다. 연산자 +
, -
, +.
, -.
, &
, &&
, %
및 %%
는 전위 연산자 또는 중위 연산자일 수 있습니다. 정의된 전위 연산자의 시작 부분에 ~
을 추가하여 이러한 연산자의 전위 버전을 중위 버전과 구분합니다. ~
은 연산자를 사용할 때 사용되지 않고 정의된 경우에만 사용됩니다.
예시
다음 코드에서는 연산자 오버로드를 사용하여 분수 형식을 구현하는 방법을 보여 줍니다. 분수는 분자와 분모로 표현됩니다. 함수 hcf
는 분수를 줄이는 데 사용되는 가장 큰 공통 인수를 결정하는 데 사용됩니다.
// 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())
출력:
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
전역 수준의 연산자
전역 수준에서 연산자를 정의할 수도 있습니다. 다음 코드는 연산자 +?
을 정의합니다.
let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)
위 코드의 출력은 12
입니다.
F#의 범위 지정 규칙에 따라 새로 정의된 연산자가 기본 제공 연산자보다 우선적으로 적용되므로 이러한 방식으로 일반 산술 연산자를 다시 정의할 수 있습니다.
키워드 inline
은 호출 코드에 가장 잘 통합되는 작은 함수인 전역 연산자와 함께 사용되는 경우가 많습니다. 또한 연산자 함수를 인라인으로 만들면 정적으로 확인된 형식 매개 변수를 사용하여 정적으로 확인된 제네릭 코드를 생성할 수 있습니다. 자세한 내용은 인라인 함수 및 정적으로 확인된 형식 매개 변수를 참조하세요.
참고 항목
.NET