다음을 통해 공유


연산자 오버로드

이 항목에서는 클래스 또는 레코드 형식 및 전역 수준에서 산술 연산자를 오버로드하는 방법을 설명합니다.

문법

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

비고

이전 구문에서 연산자 기호는 , +, -*/등 중 =하나입니다. 매개 변수 목록은 해당 연산자의 일반적인 구문에 나타나는 순서대로 피연산자를 지정합니다. 메서드 본문은 결과 값을 생성합니다.

연산자에 대한 연산자 오버로드는 정적이어야 합니다. 다음과 같이 +-단항 연산자에 대한 연산자 오버로드는 ~에 타일()을 사용하여 연산자가 이진 연산자가 아니라 단항 연산자임을 나타내야 합니다.

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

출력:

1 2
2 4
2 4
-2 -4

새 연산자 만들기

모든 표준 연산자를 오버로드할 수 있지만 특정 문자 시퀀스에서 새 연산자를 만들 수도 있습니다. 허용되는 연산자 문자는 !, $,, %, &*, +, -., /<=>?@^|, 및 .~ 문자는 ~ 연산자를 단항으로 만드는 특별한 의미를 하며 연산자 문자 시퀀스의 일부가 아닙니다. 모든 연산자를 단항으로 만들 수 있는 것은 아닙니다.

사용하는 정확한 문자 시퀀스에 따라 연산자는 특정 우선 순위와 결합성을 갖습니다. 연관성은 왼쪽에서 오른쪽 또는 오른쪽에서 왼쪽으로 사용할 수 있으며 동일한 수준의 우선 순위 연산자가 괄호 없이 순서대로 나타날 때마다 사용됩니다.

연산자 문자 . 는 우선 순위에 영향을 주지 않으므로 예를 들어 우선 순위 및 결합성이 일반 곱셈과 동일한 고유한 곱셈 버전을 정의하려는 경우와 같은 .*연산자를 만들 수 있습니다.

연산자는 $ 추가 기호 없이 독립 실행형이어야 합니다.

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
: Colon

사용자 지정 연산자에서의 : 사용은 부분적으로 예약되어 있습니다. 첫 번째 문자가 있는 연산자 또는 . 선행 개수 뒤의 .> 첫 번째 문자 > 가 있는 경우(예: 또는 .>:) 에서만 사용할 수 있습니다. >:

접두사 및 접두사 연산자

접두사 연산자는 함수와 마찬가지로 피연산자 또는 피연산자 앞에 배치되어야 합니다. 접두사 연산자는 두 피연산자 사이에 배치되어야 합니다.

특정 연산자만 접두사 연산자로 사용할 수 있습니다. 일부 연산자는 항상 접두사 연산자이고, 다른 연산자는 접두사 또는 접두사일 수 있고, 나머지는 항상 접두사 연산자입니다. 연산자 !또는 반복되는 시퀀스를 제외한 !=연산 ~자는 ~항상 접두사 연산자입니다. 연산+자, -, +., -., &, &&%%% 접두사 연산자 또는 접두사 연산자일 수 있습니다. 접두사 연산자가 정의되면 접두사 연산자의 시작 부분에 추가하여 ~ 이러한 연산자의 접두사 버전을 접두사 버전과 구분합니다. ~ 연산자를 사용할 때는 이 연산자가 정의되어 있는 경우에만 사용되지 않습니다.

예시

다음 코드에서는 연산자 오버로드를 사용하여 분수 형식을 구현하는 방법을 보여 줍니다. 소수는 숫자 및 분모로 표시됩니다. 이 함수 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 는 호출 코드에 가장 잘 통합되는 작은 함수인 전역 연산자에서 자주 사용됩니다. 또한 연산자 함수를 인라인으로 설정하면 정적으로 확인된 형식 매개 변수를 사용하여 정적으로 확인된 제네릭 코드를 생성할 수 있습니다. 자세한 내용은 인라인 함수 및정적으로 확인된 형식 매개 변수를 참조하세요.

참고하십시오