이해하기 쉬운 연산자 오버로딩의 예 [닫힘]


12

C #을 배우는 동안 C #이 연산자 오버로드를 지원한다는 것을 알았습니다. 좋은 예에 문제가 있습니다.

  1. 이해하십시오 (예 : 양과 소라는 클래스 추가)
  2. 두 문자열을 연결 한 예가 아님

기본 클래스 라이브러리의 예제를 환영합니다.


10
'센스'를 정의하십시오! 진지하게,이 요점에 대한 쓴 논쟁은 정확히 이것에 대해 큰 의견 차이가 있음을 보여줍니다. 많은 당국은 지나치게 예기치 않은 일을 할 수 있기 때문에 과부하 된 운영자를 거부합니다. 다른 사람들은 메소드 이름도 마찬가지로 직관적이지 않은 것으로 선택할 수 있다고 대답했지만, 명명 된 코드 블록을 거부 할 이유는 없습니다! 일반적으로 합리적인 것으로 간주되는 예는 거의 얻지 못할 것입니다. 당신에게 합리적인 예 -아마도.
Kilian Foth

@KilianFoth에 완전히 동의하십시오. 궁극적으로 컴파일하는 프로그램은 컴파일러에 의미가 있습니다. 그러나 ==곱셈을하기 위해 과부하 가 걸리면 나에게 이해가되지만 다른 사람들에게는 이해 가되지 않을 수 있습니다! 어떤 시설 프로그래밍 언어의 정당성에 관한이 질문입니까? 아니면 '코딩 모범 사례'에 대해 이야기하고 있습니까?
Dipan Mehta

답변:


27

적절한 연산자 오버로딩의 명백한 예는 숫자가 작동하는 것과 같은 방식으로 작동하는 클래스입니다. 따라서 Jalayn이 제안한 BigInt 클래스 , 복소수 또는 행렬 클래스 ( Superbest가 제안한)는 모두 일반 숫자가 수학 연산자에 실제로 매핑하는 것과 동일한 연산을 갖지만 시간 연산 ( svick이 제안한 대로 )은 서브 세트에 훌륭하게 매핑됩니다. 그 작업 중.

약간 더 추상적으로, 연산자는 set like 연산을 수행 할 때 사용될 operator+수 있으므로 union , 보완 등이 operator-될 수 있습니다 . 이것은 특히 더하기 또는 곱하기 연산자를 사용하는 경우 패러다임을 확장하기 시작합니다. 예상대로 t commutative .

C # 자체에는 숫자아닌 연산자 오버로드 의 훌륭한 예가 있습니다. 델리게이트 를 사용 +=하여 델리게이트-= 를 추가 및 제거 합니다. 즉, 델리게이트 를 등록 및 등록 해제합니다. 이것은 +=and -=연산자가 예상대로 작동 하기 때문에 잘 작동하므로 훨씬 간결한 코드가 생성됩니다.

순수 주의자에게는 문자열 +연산자 의 문제점 중 하나는 정식 이 아니라는 것입니다. "a"+"b"와 동일하지 않습니다 "b"+"a". 이 예외 는 문자열에 대한 일반적인 예외 이기 때문에 이해 하지만 operator+다른 유형에 사용 하는 것이 정식 적인지 아닌지 어떻게 알 수 있습니까? 대부분의 사람들은 객체가 문자열과 같지 않다고 가정 하지만 사람들이 실제로 무엇을 가정할지 알 수 없습니다.

문자열과 마찬가지로 행렬의 foibles도 잘 알려져 있습니다. 예를 들어 행렬 곱셈 (즉, 내적 곱 곱셈 행렬Matrix operator* (double, Matrix) ) 인 경우 스칼라 곱셈 임을 알 Matrix operator* (Matrix, Matrix)수 있습니다 .

마찬가지로 대리자와 함께 연산자를 사용하는 것은 수학에서 너무 멀리 제거되어 실수를 범할 가능성이 없습니다.

또한 2011 년 ACCU 회의 에서 Roger Orr & Steve Love일부 객체는 다른 객체보다 평등, 가치 및 정체성의 많은 의미를 살펴 보는 세션을 발표했습니다 . 그들의 슬라이드를 다운로드 할 수 있습니다 리처드 해리스 '있는 그대로, 부동 소수점 평등에 대한 부록 . 요약 : 매우주의operator==여기, 용 수!

연산자 오버로딩은 매우 강력한 의미 론적 기술이지만 사용하기 쉽습니다. 이상적으로는 과부하 된 연산자의 영향이 무엇인지 컨텍스트에서 매우 분명한 상황에서만 사용해야합니다. 여러 가지면에서 a.union(b)보다 명확 a+b하고 a*b있습니다 훨씬 더 애매한 이상의 a.cartesianProduct(b)카티 제품의 결과가 될 것 특히 이후, SetLike<Tuple<T,T>>(A)보다 다소을 SetLike<T>.

프로그래머 오버로드의 실제 문제는 프로그래머가 클래스가 어떤 방식으로 동작한다고 가정 할 때 발생하지만 실제로는 다른 방식으로 동작합니다. 이런 종류의 의미 충돌은 피하는 것이 중요하다고 제안하는 것입니다.


1
행렬의 연산자는 실제로 잘 매핑되지만 행렬 곱셈도 정식이 아닙니다. 또한 델리게이트의 연산자는 더욱 강력합니다. d1 + d2동일한 유형의 두 대의원에 대해 수행 할 수 있습니다 .
svick

1
@Mark : "dot product"는 벡터에만 정의됩니다. 두 행렬을 곱하는 것을 간단히 "행렬 곱셈"이라고합니다. 구별은 의미 론적 이상의 의미를 갖습니다. 내적은 스칼라를 반환하지만 행렬 곱셈은 행렬을 반환합니다 (비정규적임) .
BlueRaja-대니 Pflughoeft

26

나는 아무도 BCL에서 더 흥미로운 사례 중 하나를 언급하지 놀라게 해요 : DateTimeTimeSpan. 당신은 할 수 있습니다 :

  • TimeSpan다른을 얻기 위해 두 s를 더하거나 빼다TimeSpan
  • TimeSpan부정 에 대한 단항 빼기를 사용 하여TimeSpan
  • DateTimes를 빼면TimeSpan
  • TimeSpana DateTime를 더하거나 빼서 다른 것을 얻는다DateTime

있는 유형의 많은에 의미를 만들 수있는 사업자의 또 다른 세트 <, >, <=, >=. 예를 들어 BCL에서 Version구현합니다.


Pedantic 이론보다는 실제 사례!
SIslam

7

내 마음에 오는 첫 번째 예는 BigInteger 의 구현이며 , 큰 부호있는 정수로 작업 할 수 있습니다. 얼마나 많은 연산자가 오버로드되었는지 확인하려면 MSDN 링크를 확인하십시오 (즉, 큰 목록이 있으며 모든 연산자가 오버로드되었는지 확인하지는 않았지만 확실히 그렇게 보입니다)

또한 Java 도하 고 Java는 과부하 연산자를 허용하지 않기 때문에 작성하는 것이 훨씬 더 달콤합니다

BigInteger bi = new BigInteger(0);
bi += 10;

Java에서보다 :

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));

5

Irony 로 장난을해서 연산자 오버로드를 많이 사용 했기 때문에 이것을 보니 기쁩니다 . 다음은 수행 할 수있는 작업 의 샘플 입니다.

Irony는 ".NET 언어 구현 키트"이며 파서 생성기 (LALR 파서를 생성)입니다. yacc / lex와 같은 파서 생성기와 같은 새로운 구문 / 언어를 배우지 않고 연산자 오버로드로 문법을 C #으로 작성합니다. 간단한 BNF 문법 은 다음과 같습니다.

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

그래서 그것은 간단한 작은 문법입니다 (BNF를 배우고 문법을 구성 할 때 불일치가 있으면 실례합니다). 이제 C #을 보자.

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

보다시피, 연산자 오버로딩을 통해 C #으로 문법을 쓰는 것은 BNF로 문법을 쓰는 것과 거의 같습니다. 나에게 이것은 의미가있을뿐만 아니라 연산자 과부하를 많이 사용합니다.


3

주요 예는 operator == / operator! =입니다.

두 객체를 참조가 아닌 데이터 값으로 쉽게 비교하려면 .Equals (및 .GetHashCode!)를 오버로드하고 일관성을 위해! = 및 == 연산자를 수행하려고 할 수 있습니다.

C #에서 다른 연산자의 과도한 과부하를 본 적이 없습니다 (유용한 경우가 있다고 생각합니다).


1

MSDN의이 예제는 복소수를 구현하고 일반 + 연산자를 사용하는 방법을 보여줍니다.

또 다른 예 는 매트릭스 추가를 위해이를 수행하는 방법과 차고에 자동차를 추가하는 데 사용하지 않는 방법을 설명합니다 (링크 읽기).


0

과부하를 잘 사용하는 것은 드물지만 일어날 수 있습니다.

overloading operator == and operator! = 두 가지 생각의 학교를 보여주십시오 : 말하는 것이 더 쉽고, 말하는 것에 반대하는 사람들은 주소를 비교하는 것을 막습니다 (예 : 나는 단지 같은 사본 만 기억하는 것이 아니라 같은 장소를 기억하고 있습니다) 목적).

특정 상황에서 캐스트 연산자 과부하가 유용하다는 것을 알았습니다. 예를 들어, XML에서 0 또는 1로 표시되는 부울을 직렬화 / 역 직렬화해야했습니다. 오른쪽 (암시 적 또는 명시 적, 잊어 버린) 캐스트 연산자를 부울에서 int로 캐스트하고 다시 트릭을 수행했습니다.


4
주소 비교를 막을 수는 없습니다 object.ReferenceEquals(). 계속 사용할 수 있습니다 .
dan04

@ dan04 아주 아주 좋습니다!
MPelletier

주소를 비교하는 또 다른 방법은 ==캐스팅 을 통해 객체를 강제로 사용하는 것입니다 . (object)foo == (object)bar항상 참조를 비교합니다. 그러나 ReferenceEquals()@ dan04에서 언급했듯이을 선호 합니다. 왜냐하면 그것이하는 것이 더 명확하기 때문입니다.
svick

0

그들은 사람들이 운영자 과부하 일을 생각할 때 일반적으로 생각하는 범주에 속하지 않지만 과부하 할 수있는 가장 중요한 연산자 중 하나는 전환 연산자 라고 생각 합니다.

변환 연산자는 특히 숫자 형식으로 "설탕 제거"되거나 일부 상황에서 숫자 형식처럼 작동하는 값 형식에 유용합니다. 예를 들어, 특별한 정의 할 수 있습니다 Id특정 식별자를 표시하는 유형을, 당신은 제공 할 수있는 암시 적 으로 변환을 int당신이를 통과 할 수 있도록 Id을 소요하는 방법에 int있지만 EXPLICT에 의 변환 int에는 Id아무도는 통과 수 있도록 intId먼저 캐스팅하지 않고 가져 오는 메소드 .

C # 외부의 예로서, 파이썬 언어에는 오버로드 가능한 연산자로 구현되는 많은 특수 동작이 포함되어 있습니다. 여기에는 in멤버쉽 테스트를위한 ()연산자, 함수 인 것처럼 개체를 호출하는 len연산자 및 개체의 길이 또는 크기를 결정하는 연산자가 포함됩니다.

그리고 Haskell, Scala 및 다른 많은 기능 언어와 같은 언어가 있습니다. 여기서 이름 +은 평범한 함수이며 연산자는 아닙니다.


0

포인트 구조체System.Drawing의 네임 스페이스 연산자 오버로딩을 이용하여 두 개의 서로 다른 위치를 비교 과부하 용도.

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

보시다시피, 과부하를 사용하여 두 위치의 X 및 Y 좌표를 비교하는 것이 훨씬 쉽습니다.


0

수학 벡터에 익숙한 경우 +연산자 를 오버로드 할 때 유용하게 사용될 수 있습니다 . 및 로 벡터 a=[1,3]를 추가 할 수 있습니다 .b=[2,-1]c=[3,2]

등호 (==)를 오버로드하는 것도 유용 할 수 있습니다 ( equals()메소드 를 구현하는 것이 더 나을지라도 ). 벡터 예제를 계속하려면 :

v1=[1,3]
v2=[1,3]
v1==v2 // True

-2

폼에 그림을 그리는 코드를 상상해보십시오

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

다른 일반적인 예는 벡터 형태로 위치 정보를 유지하기 위해 구조를 사용하는 경우입니다.

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

나중에 사용하기 위해서만

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}

4
당신은 벡터가 아닌 위치를 추가 : \이 때의 좋은 예입니다 operator+해야 하지 과부하 (당신이 벡터의 측면에서 지점을 구현할 수 있지만,이 두 점을 추가 할 수 없습니다한다)
BlueRaja - 대니 Pflughoeft

@ BlueRaja-DannyPflughoeft : 다른 위치를 생성하기 위해 위치를 추가하는 것은 의미가 없지만 벡터를 생성하기 위해 위치를 빼면 평균을 계산 하는 것처럼 의미가 없습니다 . 를 통해 p1, p2, p3 및 p4의 평균을 계산할 수는 p1+((p2-p1)+(p3-p1)+(p4-p1))/4있지만 다소 어색한 것 같습니다.
supercat

1
아핀 지오메트리에서는 덧셈, 스케일링 등과 같은 점과 선으로 대수를 수행 할 수 있습니다. 그러나 구현에는 균일 한 좌표가 필요하지만 일반적으로 3D 그래픽에 사용됩니다. 두 점을 더하면 실제로 평균이됩니다.
ja72
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.