본문 바로가기
Programming/C++

C++ 언어 기초 (9) - 연산자 다중정의

by 롱일스 2020. 8. 31.
반응형

▣ 연산자 다중정의?

 ●  연산자 다중정의란?

  ▷ C++에 정의된 연산자를 사용자가 선언한 클래스 객체에서 사용할 수 있게 정의하는 것을 말한다.

 

 ●  연산자 다중정의 규칙

  ▷ 연산자의 의미를 임의로 바꾸면 안된다. 예를 들어, 덧셈을 뺄셈으로 바꾸는 행위는 안된다.

  ▷ '연산자의 우선순위'나 '피연산자 수'와 같은 연산자의 고유한 특성이 유지되도록 만든다.

 

 ●  연산자 다중정의를 사용하는 경우

  ▷ 클래스의 객체 간 대입/이동 대입 연산자의 경우

  ▷ 수치형 객체의 산술 연산자

  ▷ 두 객체를 비교하기 위한 관계 연산자

  ▷ 스트림 입출력을 위한 >> 와 << 연산자

 

 ●  연산자 다중정의를 사용할 수 없는 경우

  ▷ 멤버 선택 연산자 - .

  ▷ 멤버에 대한 포인터 연산자 - .*

  ▷ 유효범위 결정 연산자 - ::

  ▷ 조건 연산자 - ? :

 

 ●  연산자 다중정의 위치

  1) 클래스의 멤버로 정의

   ▷ 연산자 구현 과정에서 객체 멤버에 접근 가능하다.

  2) 클래스 외부에서 정의

   ▷ 클래스 내부에서 정의할 수 없는 경우에 이렇게 정의하는데 클래스의 멤버는 아니라서 객체의 private 멤버는 사용할 수 없다.

 

▣ 단항 연산자 다중정의

 ●  단항 연산자

  ▷ 피연산자가 1개인 연산자를 의미하고 전위 표기법과 후위 표기법으로 나뉜다.

A = 10일 때 단항 연산자 수식 실행 결과
A B
B = ++A ; 11 11
B = A++ ; 11 10
B = --A ; 9 9
B = A-- ; 9 10

 

 ●  단항 연산자 다중정의 형식

  ▷ 전위표기법 단항 연산자 다중정의

ReturnClass ClassName::operator opSymbol()
{
	.....
}

//opSymbol: ++, -- 등 단항 연산자
// 형식 매개변수는 비어져있다.

  ▷ 전위표기법 단항 연산자 다중정의 예시

class Sample1 {
    int a;
public:
    /*생성자*/
    Sample1(int n = 0) : a(n) { }
    /*단항연산자 다중정의*/
    Sample1& operator ++ () {  //형식 매개변수 비워놓기
        ++a;
        return *this; //자기 자신 객체(Sample1)를 return하는 것
    }
    int meThod() const {return a};
};


Sample1 h;
cout << (++h).meThod() << endl;  // 1 출력

  ▷ 후위표기법 단항 연산자 다중정의

     형식 매개변수로 int를 써서 전위가 아닌 후위 표기법임을 나타낸다. 

ReturnClass ClassName::operator opSymbol(int)
{
	.....
}

//opSymbol: ++, -- 등 단항 연산자
// 형식 매개변수로 int를 써서 전위가 아닌 후위 표기법임을 나타낸다. 
// 꼭 정수만 전달할 필요는 없다.

  ▷ 후위표기법 단항 연산자 다중정의 예시

class Sample2 {
    int a;
public:
    /*생성자*/
    Sample2(int n = 0) : a(n) { }
    /*단항연산자 다중정의*/
    Sample2& operator ++ (int) {  //형식 매개변수 int --> 후위표기
        Sample2 tmp(*this)
        ++a;
        return tmp; //후위 표기이기 때문에 객체가 변화되기 이전의 값을 return하고 객체 자체는 변화됐다.
    }
    int meThod() const {return a};
};


Sample2 h;
cout << (h++).meThod() << endl;  // 0 출력

 

 ●  단항 연산자 예제

  ▷ 예제 - Pencils 클래스

멤버함수 설명
Pencils() 생성자, 0으로 초기화
Pencils(int n) 생성자, n을 타와 낱개로 변환
Pencils(int d, int n) 생성자, d타 n자루로 초기화
Pencils& operator ++() 전위 표기 ++ 연산자
Pencils operator ++(int) 후위 표기 ++ 연산자
void display() 내용 출력
데이터 멤버 설명
int dozens 타 수
int np 낱개의 수

  ▷ Pencils 클래스 - Pencils.h

#ifndef PENCILS_H_INCLUDED
#define PENCILS_H_INCLUDED
using namespace std;

class Pencils {
    int dozens;
    int np;
public:
    Pencils() : dozens(0), np(0) {};
    Pencils(int n)
        { dozens = n / 12; np = n % 12; }
    Pencils(int d, int n) : dozens(d), np(n) {}
    
    /*연산자 다중정의*/
    // ++ 전위 연산자    
    Pencils& operator ++ () {  //객체의 값이 연산에 의해 바뀌므로 const 설정하지 않는다.
       if (++np >= 12)      //낱개를 1 증가시키고 결과가 12보다 크면
          ++dozens, np = 0; // 타 수를 1 증가시키고, 낱개는 0으로 지워준다.
       return *this;        // 증가된 결과 반환
    }
    
    // ++ 후위 연산자
    Pencils operator ++ (int) {
       Pencils tmp(*this);    //현재 객체 보존
       if (++np >= 12)       // 낱개를 1 증가시키고 결과가 12보다 크면
          ++dozens, np = 0;  // 타 수 1 증가시키고 낱개 0
       return tmp;          // 보존된 객체 반환
    }
    
    void display() const   //출력하면서 객체 멤버 값이 변하지 않도록 const 선언한다.
    {
       if (dozens) {
          cout << dozens << "타" ;
          if (np) cout << np << "자루";  //np가 0이 아닐 때만 출력하게끔
             cout << endl;
       }
       else
          cout << np << "자루" << endl;
    }
};

#endif

  ▷ Pencils 클래스 - main.cpp

#include <iostream>
#include "Pencils.h"
using namespace std;

int main()
{
    Pencils p1(7, 7);
    Pencils p2(123);
    
    p1.display();
    (++p1).display();
    p1.display();
    cout << endl;
    p2.display();
    p1 = p2++;
    p1.display();
    p2.display();
    return 0;
}

  ▷출력 결과

 

 

▣ 이항 연산자 다중정의

 ●  이항 연산자 다중정의 형식

   연산자를 사용하는 객체 자신이 왼쪽 피연선자, 매개변수 arg가 오른쪽 피연산자

ReturnClass ClassName::operator opSymbmol(ArgClass arg)
{
	....
}

//opSymbol은 +, -, *, /, %, ||, && 등 이항 연산자 기호
//연산자를 사용하는 객체 자신이 왼쪽 피연선자, 매개변수 arg가 오른쪽 피연산자

  ▷ 이항 연산자 예시 - (복소수 객체 + 복소수 객체의 덧셈 연산자)

/* complex2Obj1 + complex2Obj2 수식 구현을 위한 연산자 정의*/
Complex2 Complex2::operator + (const Complex2 &c) const
{
    Complex2 tmp(*this);
    tmp.rPart += c.rPart;
    tmp.iPart += c.iPart;
    return tmp;
}

// 위와 동일하게 아래와 같이 간단하게 작성할 수도 있다.
Complex2 Complex2::operator + (const Complex2 &c) const
{
    return Complex2(rPart + c.rPart, iPart + c.iPart); 
    //임시 객체를 만들어 return. 객체 이름이 따로 없다.
}

 

  ▷ 이항 연산자 예시 - (복소수 객체 + 실수의 덧셈 연산자)

   동일한  + 연산자에 대해 다른 정의를 할 수 있다. 다중정의니까~

/* complex2Obj1 + 3.0 수식 구현을 위한 연산자 정의*/
Complex2 Complex2::operator + (double r) const
{
    return Complex2(rPart + r, iPart); 
}

  위 연산자 정의의 경우 이렇게 연산자를 정의하지 않아도 Complex2(double r = 0, double i = 0)의 생성자를 통해 double값이 묵시적으로 Complex2 객체로 형변환되어 객체간 덧셈 연산자 정의를 통해 수식 처리가 가능하다.

 

  ▷ 이항 연산자 예시 - (실수 + 복소수 객체의 덧셈 연산자)

  • 왼쪽 피연산자가 실수라서 Complex2 클래스 멤버로는 연산자를 정의할 수 없다.
  • 그래서 클래스에 안 속하는 외부 별도 연산자로 정의해야 한다.
/* 3.0 + complex2Obj1  수식 구현을 위한 연산자 정의*/
Complex2 operator + (double r, const Complex2 &c)
{
    return Complex2(r + c.rPart, c.iPart); 
    // c.rPart와 c.iPart는 클래스에 속해있는 private 멤버라서 오류가 발생한다.
}

 c.rPart와 c.iPart는 클래스에 속해있는 private 멤버라서 오류가 발생한다. 이 오류를 해결하기 위한 방법은 2가지가 있다.

(Solution 1) Complex2에 private 멤버를 액세스할 수 있는 멤버함수를 따로 정의해준다.

class Complex2 {
   double rPart, iPart;
public:
   .....
   double real() const {return rPart;}  
   double imag() const {return iPart;}
};
Complex operator + (double r, const Copmlex2 &c)
{
   return Complex2(r + c.real(), c.imag());
}

 

(Solution 2) Complex2에서 다중정의된 연산자를 friend로 선언해준다.

class Copmlex2 {
  double rPart, iPart;
public:
  ....
  friend Copmlex2 operator + (double r, const Compelx2& c);
};
Complex2 operator + (double r, const Copmlex2 &c)
{
  return Complex2(r + c.rPart, c.iPart);
}

 

  ▷ 이항 연산자 예시 - (복소수 객체의 복합 대입 연산자)

  반환형은 Complex2&으로 자신에 대한 참조를 반환한다.

/* complex2Obj1 += complex2Obj2  수식 구현을 위한 연산자 정의*/
Complex2& Complex2::operator += (const Complex2 &c)
{
    rPart += c.rPart;  
    iPart += c.iPart;
    return *this;
}

 

 

▣ 스트림 출력 연산자 다중정의

 ●  스트림 출력 연산자(<<) 다중정의

  ▷ << 연산자 정의할 위치

Complex2 c(1.0, 2.0);
cout << c;
  • 왼쪽 피연산자인 cout은 표준 C++라이브러리에서 정의한 것이므로 Complex2의 객체가 아니라 일반적으론 수정이 불가능하다.
  • 따라서 클래스에 속하지 않는 외부 별로 연산자로 정의해야만 한다.
  • 그리고 << 연산자가 Copmlex2 객체의 private 멤버에 접근 가능하게 friend로 지정한다.

 

  ▷ << 연산자 반환값

cout << "a에는 다음의 값이 저장되어 있다." << a;

 

 ●  스트림 출력 연산자 다중정의 예시

class Complex2 {
   public:
    
    Complex2(double r=0, double i=0):rPart(r), iPart(i) {}
    
    Complex2 operator + (const Complex2 &c) const {
        Complex2 tmp(*this);
        tmp.rPart += c.rPart;
        tmp.iPart += c.iPart;
        return tmp;
    }
   
   /* << 연산자 다중정의 */
    friend ostream& operator<<(ostream &os, const Complex2 &c);
};
/*왼쪽항 cout(os) || 우측항 대입할 변수(c)*/
ostream& operator<<(ostream& os, const Complex2& c)
{
   os << "(" << c.rPart;
   if (c.iPart > 0)
     os << "+j" << c.iPart;
   else if (c.iPart < 0)
     os << "-j" << -c.iPart;
   os << ")";
   return os;
}
#include <iostream>
#include "Complex2.h"
using namespace std;

int main()
{
   Complex2 a(12, 34);
   Complex2 b(10, -12);
   cout << a << " + " << b << " = " << a + b << endl;
   return 0;
}

 

  ▷출력 결과

 

728x90
반응형