다음을 통해 공유


연산자 오버로드(F#)

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

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

설명

위 구문에서 operator-symbol은 +, -, *, /, = 등이 될 수 있습니다. parameter-list는 해당 연산자의 일반적인 구문에 표시되는 순서에 따라 피연산자를 지정합니다. 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#)에 나오는 표를 참조하십시오.

오버로드된 연산자 이름

F# 컴파일러에서 연산자 식을 컴파일할 때는 해당 연산자에 대해 컴파일러를 통해 생성된 이름을 사용하는 메서드가 만들어집니다. 이는 해당 메서드에 대해 MSIL(Microsoft intermediate language)에 표시되는 이름입니다. 리플렉션과 IntelliSense에도 이 이름이 표시됩니다. 일반적으로 F# 코드에는 이러한 이름을 사용할 필요가 없습니다.

다음 표에는 표준 연산자와 그에 상응하는 기본 이름이 나와 있습니다.

Operator

기본 이름

[]

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

목록에 나열되어 있지 않은 다른 연산자 문자 조합을 연산자로 사용할 수도 있습니다. 여기에는 다음 표에 나와 있는 개별 문자의 이름을 결합하여 만든 이름이 사용됩니다. 예를 들어 +!의 이름은 op_PlusBang이 됩니다.

연산자 문자

Name

>

Greater

<

Less

+

Plus

-

Minus

*

Multiply

/

Divide

=

Equals

~

Twiddle

%

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

전역 수준 연산자

전역 수준에서 연산자를 정의할 수도 있습니다. 다음은 +? 연산자를 정의하는 코드입니다.

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

위 코드의 출력은 12입니다.

이와 같은 방법으로 일반적인 산술 연산자를 재정의할 수 있습니다. F#의 범위 지정 규칙에 따르면 새로 정의한 연산자가 기본 제공 연산자보다 우선 순위가 높기 때문입니다.

키워드 inline은 일반적으로 호출 코드에 가장 잘 통합되는 작은 함수인 전역 연산자와 함께 사용되는 경우가 많습니다. 연산자 함수를 인라인으로 만들면 정적으로 확인된 형식 매개 변수에 해당 함수를 사용하여 정적으로 확인된 제네릭 코드를 만들 수도 있습니다. 자세한 내용은 인라인 함수(F#)정적으로 확인된 형식 매개 변수(F#)를 참조하십시오.

참고 항목

개념

멤버(F#)