Creative Commons License

Microsoft .NET

닷넷!시작하기
닷넷! Ver 2.0~
닷넷!스킬업
웹개발
윈폼개발
실용모듈개발
Tip & Tech
하루 한 문법

Microsoft .NET 개발자들을 위한 공간입니다. 기초강의에서 부터 고급 기술 정보 및 팁등을 다루도록 하겠습니다.

.

닷넷!시작하기

Microsoft. NET 을 시작하는 분들을 위한 강좌입니다. 주로 기초적인 내용과 때론 기본적인 내용을 다룹니다

[C# 기초강좌] 14. C# 연산자 오버로딩

작성자 : 박종명
최초 작성일 : 2010-07-02 (금요일)
최종 수정일 : 2010-07-02 (금요일)
조회 수 : 6738

연산자 오버로딩, 이것 역시 메서드 입니다

 

안녕하세요. 박종명입니다. 닷넷 열네 번째 강좌를 진행하도록 하겠습니다

이번 강좌는 C#에서 연산자를 새로이 정의할 수 있는 기법인 연산자 오버로딩에 대해 알아 보겠습니다

 

연산자 오버로딩은 사칙연산과 같이 기본 제공되는 +, -, *, / 연산자는 물론 비교 연산자, 형 변환 연산자 등을

오버로드 하여 새로운 연산 논리를 구현하는 기법으로써 오버로드 된 연산자 역시 결국에는 메서드입니다

 

 

숫자 형식 데이터를 다루는데 있어 사칙연산은 프로그램의 생명 주기에 상당히 많이 차지하는 기본 연산입니다.

int a = 1; int b = 2;

int sum = a + b;

 

숫자 형식은 그렇다 치더라도 사용자 정의 자료형인 객체나 구조체끼리의 사칙연산이 가능할까요?

Pserson 이라는 클래스의 객체 person1 person2에 대해 다음과 같이 더하기(+) 연산은 가능할까요?

 

Person p1 = new Person ();

Person p2 = new Person ();

Person sumPerson = p1 + p2;

 

코드를 실행해 볼 필요도 없이 이 연산은 컴파일 오류를 발생시킵니다

 

현실적으로 사람을 추상화 한 Person 객체에 더하기를 수행할 이유는 없어 보입니다.

그러나 만일 정말 만일에, 두 사람을 더하면(+) 두 사람의 나이(age)가 더해져서 반환되도록 해야 한다면

어떻게 하는 것이 좋을까요?

 

언뜻 AddPerson(Person p1, Person p2) 이라는 메서드를 생각해 볼 수 있습니다

좋은 선택입니다. 그럼 두 사람이 아닌 셋, , 다섯 사람을 더해야 한다면 아래와 같은 코드가 예상됩니다

 

Person sumPerson = Person.AddPerson(Person.AddPerson(Person.AddPerson(p1, p2), p3),p4);

 

코드는 총 4 사람(Person)에 대해 더하기를 수행하고 있습니다

뭔가 복잡해 보입니다. 숫자를 더하듯 할 수 있다면 간편하지 않을까요? 아래와 같이요.

 

Person sumPerson = p1 + p2 + p3 + p4;

 

훨씬 간단해 보입니다. 이것이 가능할까요?

연산자 오버로딩은 바로 이것입니다. 객체와 같은 사용자 정의 타입에 연산자를 새롭게 정의 할 수 있도록
해 주는 기법입니다.

 

이때 연산자는 사칙연산과 같이 기본 제공되는 연산자 키워드를 기반으로 다시 정의하는 것이기 때문에

연산자 오버로딩이라고 합니다. 메서드 오버로딩이 그러하듯 이요

 

 

복소수 클래스의 연산자 오버로딩

수학에서 복소수라 함은 실수와 허수의 합으로 이루어진 수 인데요. 다음과 같이 표현합니다

a + bi (a, b는 실수, i2 = -1을 만족하는 허수단위)

 

복소수의 수학적 개념보다도 우리는 이 복소수를 표현하고 연산하기 위해 프로그램을 작성한다고 가정합니다

 

다음과 같이 실수부와 허수부에 해당하는 멤버 변수 2개를 가진 복소수 클래스를 정의하고

생성자를 통해 값을 초기화 합니다. 그리고 ToString() 메서드에서 복소수 표현식인 a + bi 형태로 반환하도록

오버라이딩 해 둡니다

 

class Complex{

    public int real;     //실수부

    public int imaginary; //허수부

 

    public Complex(int real, int imaginary) {

            this.real = real;

            this.imaginary = imaginary;

    }               

       

    public override string ToString(){

        return (System.String.Format("{0} + {1}i", real, imaginary));

    }

}

 

복소수의 사칙 연산

복소수에 사칙연산을 수행할 수 있는데요.

두 복소수 (a + bi) (c + di)의 덧넷, 뺄셈, 곱셈은 다음과 같이 계산할 수 있습니다

덧셈: (a + bi) + (c + di) = (a + c) + (b + d)i

뺄셈: (a + bi) - (c + di) = (a - c) + (b - d)i

곱셈: (a + bi) * (c + di) = (ac - bd) + (bc + ad)i

 

우리가 정의한 복소수 클래스(Complex)에 이러한 사칙연산을 미리 정의해 두면 이 클래스를 사용하는 입장에서 편리하겠죠. 그리고 클래스 구조적으로도 사칙연산의 행위를 미리 정의해 두는 것이 좋습니다

 

물론 메서드를 통해 각각의 연산 메서드를 정의할 수 있겠지만 우리는 연산자 오버로딩을 통해 구현하도록 합니다

 

 

operator X 키워드

연산자 오버로딩을 위해서 사용되는 키워드는 operator 입니다. 그리고 X 부분은 오버로딩할 연산자입니다

우리는 덧넷,뺄셈,곱셈을 정의할 것이기에 X +, - , * 이 되겠네요

 

아래 세 연산자를 오버로드 한 코드입니다

public static Complex operator + (Complex c1, Complex c2){

    return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);

}

 

public static Complex operator - (Complex c1, Complex c2){

    return new Complex(c1.real - c2.real, c1.imaginary - c2.imaginary);

}

 

public static Complex operator * (Complex c1, Complex c2){

    return new Complex((c1.real * c2.real) - (c1.imaginary * c2.imaginary),

                                (c1.imaginary * c2.real) + (c1.real * c2.imaginary));

}

 

세가지 연산이 정의되었기 때문에 다음과 같이 복소수 연산을 일반 연산처럼 쉽게 이용할 수 있습니다

 

static void Main(string[] args){           

    Complex num1 = new Complex(4, 3);

    Complex num2 = new Complex(3, 2);

           

    Complex add = num1 + num2;

    System.Console.WriteLine("복소수 합: {0}", add);

 

    Complex minus = num1 - num2;

    System.Console.WriteLine("복소수 차: {0}", minus);

 

    Complex multi = num1 * num2;

    System.Console.WriteLine("복소수 곱: {0}", multi);           

}

 

마치 일반 숫자 데이터의 사칙연산을 하듯이 객체의 사칙연산이 가능해 졌습니다

물론 메서드로 이러한 연산을 제공해 줄 수도 있지만 연산자 오버로딩을 구현하면 메서드로 처리하는 것보다

직관적이며 명확해져 편리성과 잠재적 버그 유발성을 줄일 수 있는 장점이 있습니다

 

 

연산자 오버로딩 규칙

연산자를 오버로딩하는 언어적 규칙이 몇 가지 있습니다. 이를 소개합니다

 

- 연산자 오버로딩은 operator X 키워드로 구현한다

- 반드시 public 로 정의되어야 한다

- 반드시 static 로 정의되어야 한다

 

- 입력 매개변수 중 하나 이상은 반드시 그 클래스의 형식과 동일해야 한다

public static Complex operator + (Complex c1, int i){ .. }  //가능

public static Complex operator + (int i, int j) { .. }      //불가능

 

- 연산자 오버로딩을 다시 오버로딩 할 수 있다

연산자 오버로딩도 결과적으로는 메서드이다. 따라서 시그너처를 달리 하면 이미 정의된 연산

오버로딩을 다시 오버로딩 할 수 있다.

아래 코드는 더하기(+) 연산자를 시그너처를 달리 하여 두 개 오버로딩 한 예시이다

 

public static Complex operator + (Complex c1, Complex c2){

      return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);

  }

  public static Complex operator + (Complex c1, int i){

      return new Complex(c1.real + i, c1.imaginary + i);

  }

 

연산자 오버로딩도 결국 메서드라 하였는데요.

 IL 코드를 보면 덧넷,뺄셈,곱셈에 대한 각각의 연산자 오버로딩이 op_Addition, op_Subtraction, op_Multiply 메서드로

자동 생성된 것을 확인할 수 있습니다.

 

이렇듯 연산자 오버로딩을 하게 되면 op_예약된심벌형태의 메서드로 치환되는 것입니다

비교연산자 오버로딩

지금까지는 사칙연산에 대한 연산자 오버로딩을 알아 보았는데요

연산자 오버로딩은 사칙연산뿐만 아니라 다른 연산자에 대한 오버로딩도 가능합니다

 

비교연산자의 오버로딩에 대해 알아 보겠습니다. C#의 비교 연산자는 다음과 같습니다

 

1) 동일성 비교

== : 두 값의 일치 여부, != : 두 값의 불일치 여부

2) 크기 비교

< : ‘보다 작음비교, > :  보다 큼비교

<=: ‘작거나 같음비교, >=크거나 같음비교

 

비교연산자를 자세히 보면 모두 쌍으로 이루어져 있습니다. == != < > 와 쌍을 이루고 있지요.

비교연산자를 오버로딩 할 경우 반드시 이 쌍을 모두 오버로딩 해야 합니다(필수 사항입니다)

 

예를 들어 == 연산자를 오버로딩 할 경우 반드시 그 쌍을 이루는 != 를 같이 오버로딩 해야 하는 것이죠

 

닷넷 프레임워크에서 제공하는 System.String 에서 동일성 비교 연산자를 오버로딩 한 예를 볼 수 있는데요

Msdn System.String 클래스의 설명을 보면 아래와 같이 == !=를 오버로딩 한 것을 확인할 수 있습니다

public static bool operator == (

             string a,

             string b

)

 

public static bool operator != (

             string a,

             string b

)

 

참고로 동일성 비교 연산자를 오버로딩 할 경우 Object 로부터 상속받는 Equals 메서드도 같은 연산으로

오버라이딩 하는 것이 좋습니다(프로그램 일관성을 위한 권장사항 입니다)

(나아가 Equals 메서드를 오버라이딩 할 경우 GetHashCode 메서드도 오버라이딩 하는 것이 좋습니다)

 

 

연산자 오버로딩 가능한 연산자 목록

앞서 살펴본 것과 같이 사칙연산과 비교 연산자는 연산자 오버로딩이 가능합니다

닷넷의 연산자 중 오버로딩이 가능한 연산자와 일부 제한이 있는 연산자가 있습니다

다음 표는 msdn 의 설명입니다

 

형 변환 연산자 오버로딩

마지막으로 형식 변환과 관련된 연산자 오버로딩에 대해 알아 보겠습니다

연산자 오버로딩은 산술연산자나 비교연산자 외에도 형식(Type) 변환에도 적용할 수 있습니다

 

특정 자료형을 다른 자료형으로 변환하는 것을 형 변환 이라고 하는데요.

예를 들어 문자열 정수인 int 형의 데이터를 실수인 double 형으로의 형 변환은 다음과 같이 자연스럽게 이루어 집니다.

 

int iValue = 10;

double dValue = iValue; //묵시적 형변환

 

특별히 어떤 키워드 없이도 정수형 자료가 실수 형 자료로 할당되었습니다

이것은 더 작은 자료형에서 큰 자료형으로의 변환은 자동으로 이루어 지는 즉 묵시적 형 변환사례입니다

반면 실수형 자료를 정수형 자료로 할당하고자 할 경우에는 다음 코드와 같이 반드시 명시적 형 변환
해 줘야 합니다

 

double dValue = 10d;

int iValue = (int) dValue; //명시적 형변환

 

명시적 형 변환 위한 (형식) 이라는 연산자가 이용된 것입니다

 

이것은 클래스의 상속 관계에서도 적용되는데요. ‘부모 ? 자식 클래스구조에서,

자식객체 -> 부모 객체로의 변환은 묵시적으로 이루어 지고

부모객체 -> 자식 객체로의 변환은 명시적으로 형 변환을 해 줘야 합니다

 

이러한 묵시적, 명시적 형 변환을 새롭게 정의할 수 있는 것이 형 변환 연산자 오버로딩 입니다

 

형 변환 오버로딩의 키워드는 operator X 와 더불어 다음 두 키워드가 사용됩니다

implicit: 묵시적 형 변환 오버로딩

explicit: 명시적 형 변환 오버로딩

 

그럼. 형 변환 오버로딩 예를 보겠습니다

 

Person이라는 클래스를 정의하고 이름(name)과 나이(age)를 속성으로 가지도록 합니다

그리고 정수타입(int)에서 Person 타입으로 명시적 형 변환이 가능하도록 구현하고

Person 타입에서 문자열(string)타입으로 묵시적 형 변환이 가능하도록 오버로딩 해 보겠습니다

 

class Person{

    public string name;

    public int age;

 

    public Person(string name, int age){

        this.name = name;

        this.age = age;

    }

 

    //명시적 형변환 오버로딩

    public static explicit operator Person(int age){

        return new Person("아무개", age);

    }

 

    //묵시적 형변환 오버로딩

    public static implicit operator string(Person person){

        return "제 이름은 " + person.name + "입니다";

    }

}

 

이렇게 클래스가 정의되고 클래스에서 각각의 형 변환을 위한 오버로딩 메서드가 구현되었으면 다음과 같이
사용할 수 있습니다

 

int age = 20;

Person person1 = (Person) age; //명시적 형변환 수행(int -> Person 으로 변환)

            

Person person2 = new Person("홍길동", 20);

string s = person2; //묵시적 형변환 수행(Person -> string 으로 변환)

 

참고로 string 형 변환 오버로딩을 구현 한 경우 ToString 메서드도 같이 오버라이딩 해 주는 것이 좋습니다

 

이상으로 연산자 오버로딩 강좌를 마치도록 하겠습니다

행복한 한 주 되세요 ~~~

이름
비밀번호
홈페이지
TO <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요