Home [C++ 프로그래밍] 8강 - 연산자 다중 정의
Post
Cancel

[C++ 프로그래밍] 8강 - 연산자 다중 정의

💡해당 게시글은 방송통신대학교 이병래 교수님의 'C++ 프로그래밍' 강의를 개인 공부 목적으로 메모하였습니다.



학습 개요


  • C++에는 기본 자료형의 데이터에 사용할 수 있는 여러 가지 연산자가 제공 됨
  • 연산자를 우리가 선언하는 클래스의 객체에도 사용할 수 있도록 하는 연산자 다중 정의에 대해 학습함
  • 1개의 피연산자를 갖는 단항 연산자 및 2개의 피연산자를 갖는 이항 연산자의 다중 정의 구문 형식을 익히고, 연산자를 다중 정의하는 과정에서 유의해야 할 여러 가지 사항을 학습함



학습 목표


  • 연산자 다중 정의의 개념을 설명할 수 있음
  • 단항 연산자를 다중 정의 할 수 있음
  • 이항 연산자를 다중 정의 할 수 있음
  • friend를 활용할 수 있음



주요 용어


  • 다중 정의(overloading)
    • 하나의 이름에 대하여 두 가지 이상의 서로 다른 선언을 하는 것
  • 연산자 다중 정의(operator overloading)
    • C++에 정의된 연산자를 사용자가 선언한 클래스의 객체에 대하여 사용할 수 있도록 정의 하는 것



강의록


연산자 다중 정의의 개념

연산자 다중 정의

  • 피 연산자의 자료형과 연산자
    • 동일한 연산자라도 구체적인 처리 방법은 피 연산자의 자료형에 따라 다름

      1
      2
      
        10 + 20 -> int형 + 연산자
        10.0 + 20.0 -> double형 + 연산자
      
      • 연산자는 피 연산자의 자료형에 따라 그것에 맞는 처리 절차가 정의되어 있음

C++ 언어의 연산자 다중 정의

  • 연산자 다중 정의란?
    • C++에 정의된 연산자를 사용자가 선언한 클래스의 객체에 대하여 사용할 수 있도록 정의 하는 것
  • 연산자 다중 정의를 할 때 주의 사항
    • 연산자의 의미를 임의로 바꾸지 않음
    • 연산자의 고유한 특성이 유지되도록 함
      • 연산자의 우선 순위나 피 연산자 수 불변
      • 전위 표기와 후위 표기 연산자의 의미 유지
  • 주요 연산자 다중 정의의 대상
    • 클래스의 객체 간 대입 및 이동 대입 연산자
      • 특히 동적 할당을 받는 포인터를 포함하는 경우 고려 필요가 있음
    • 수치형 객체의 산술 연산자 다중 정의
      • 교환 법칙도 함께 고려함
    • 두 객체를 비교하기 위한 관계 연산자의 다중 정의
    • 스트림 입력 및 출력을 위한 >>, <<연산자
  • 다중 정의를 할 수 없는 연산자
    • 멤버 선택 연산자 (.)
    • 멤버에 대한 포인터 연산자 (.*)
    • 유효 범위 결정 연산자 (::)
    • 조건 연산자 (? :)

연산자 다중 정의의 위치

  • 클래스의 멤버로 정의하는 방법
    • 연산자의 구현 과정에서 객체의 멤버를 액세스 할 수 있음
  • 클래스 외부에서 정의하는 방법
    • 클래스의 멤버가 아니므로, 객체의 private멤버는 직접 사용할 수 없음
      • 필요하다면 private멤버를 액세스할 수 있는 방법을 마련해야 함

단항 연산자의 다중 정의

단항 연산자

  • 단항 연산자
  • 피 연산자가 1개인 연산자
  • 전위 표기법과 후위 표기법

    수식 (a가 10일 때)실행 결과 a의 값실행 결과 b의 값
    b = ++a;1111
    b = a++;1110
    b = --a;99
    b = a--;910

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

  • 다중 정의의 형식

    1
    2
    3
    4
    
      ReturnClass ClassName::operator opSymbol()
      {
        
      }
    
    • opSymbol
      • ++, -등의 단항 연산자 기호
    • 형식 매개 변수 없음
  • 전위 표기 ++ 연산자의 다중 정의

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      class IntClass1 {
        int a;
      public:
        IntClass1(int n=0) : a(n) {}  // 생성자
        IntClass1& operator ++ () {   // 전위 표기 ++ 연산자 다중 정의
            ++a;
            return *this;
        }
        int getValue() const { return a; }
      };
    
    1
    2
    
      IntClass1 i;
      cout << (++i).getValue() << endl;   // 1
    

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

  • 다중 정의의 형식

    1
    2
    3
    4
    
      ReturnClass ClassName::operator opSymbol(int)
      {
        
      }
    
    • opSymbol
      • ++, -등의 단항 연산자 기호
    • 형식 매개 변수 표기 위치의 int는 인수 전달의 의미가 아니라 단지 후위 표기법을 사용하는 단항 연산자임을 나타냄
  • 후위 표기 ++ 연산자의 다중 정의

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      class IntClass2 {
        int a;
      public:
        IntClass2(int n=0) : a(n) {} // 생성자
        IntClass2 operator ++ (int) { // 후위 표기 ++ 연산자 다중 정의
          IntClass2 tmp(*this);
          ++a;
          return tmp;
        }
        int getValue() const { return a; }
      };
    
    1
    2
    
      IntClass2 i;
      cout << (i++).getValue() << endl; // 0
    

예제 : Pencils 클래스

  • Pencils 클래스
    • n타 m자루 형태로 연필의 개수를 표현하는 클래스를 정의한다(1타는 12자루).
    • 낱개의 수를 1개 증가 시키는 전위 및 후위 표기 ++연산자를 포함하며, 연필의 수량을 출력하는 멤버 함수를 포함한다.
  • 행위

    멤버 함수비고
    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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef PENCILS_H_INCLUDED
#define PENCILS_H_INCLUDED

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 ++ ();      // ++ 연산자(전위 표기)
  Pencils operator ++(int);     // ++ 연산자(후위 표기)
  void display() const;
};

#endif

Pencils 클래스 - Pencils.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include "Pencils.h"
using namespace std;

Pencils& Pencils::operator ++() { // ++ 연산자(전위 표기)
  if (++np >= 12) {   // 낱개를 1 증가시키고 결과가 12보다 크면
    ++dozens; np = 0; // 타 수를 1 증가시키고 낱개는 0
  }
  return *this;       // 증가된 결과를 반환
}

Pencils Pencils::operator ++(int) { // ++ 연산자(후위 표기)
  Pencils tmp(*this); // 현재 객체를 보존
  if (++np >= 12) {   // 낱개를 1 증가시키고 결과가 12보다 크면
    ++dozens; np = 0; // 타 수를 1 증가시키고 낱개는 0
  }
  return tmp;         // 보존된 객체를 반환
}

void Pencils::display() const
{
  if (dozens) {
    cout << dozens << "타 ";
    if (np) cout << np << "자루";
    cout << endl;
  }
  else
    cout << np << "자루" << endl;
}

Pencils 클래스 - PnclMain.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
  Pencils p1(5, 7);
  Pencils p2(23);

  p1.display(); // 5타 7자루
  (++p1).display(); // 5타 8자루
  p1.display(); // 5타 8자루
  cout << endl;
  p2.display(); // 1타 11자루
  p1 = p2++;
  p1.display(); // 1타 11자루
  p2.display(); // 2타
  return 0;
}

이항 연산자의 다중 정의

이항 연산자의 다중 정의

  • 다중 정의의 형식

    1
    2
    3
    4
    
      ReturnClass ClassName::operator opSymbol(ArgClass arg)
      {
        
      }
    
    • opSymbol
      • +, *, /, &&, ||등의 이항 연산자 기호
    • 객체 자신이 좌측 피 연산자, arg가 우측 피 연산자에 해당됨

예 : Complex2 클래스

  1. 복소수 객체와 복소수 객체의 덧셈 연산자
    • 수식

      1
      
        complex2Obj1 + complex2Obj2
      
      • complex2Obj1
        • *this
      • complex2Obj2
        • c
    • 구현

      1
      2
      3
      4
      5
      6
      7
      
        Complex2 Complex2::operator + (const Complex2 &c) const
        {
          Complex2 tmp(*this);
          tmp.rPart += c.rPart;
          tmp.iPart += c.iPart;
          return tmp;
        }
      
      1
      2
      3
      4
      
        Complex2 Complex2::operator + (const Complex2 &c) const
        {
          return Complex2(rPart + c.rPart, iPart + c.iPart);
        }
      
  2. 복소수 객체와 실수의 덧셈 연산자
    • 수식

      1
      
        complex2Obj + 10.0
      
    • 구현

      1
      2
      3
      4
      
        Complex2 Complex2::operator + (double r) const
        {
          return Complex2(rPart + r, iPart);
        }
      
      • Complex2(double r=0, double i=0)라는 생성자가 정의되어 있어 double값이 묵시적으로 Complex2 객체로 형 변환되므로, 이 연산자를 정의하지 않아도 Complex2 객체 간 덧셈 연산자를 이용하여 수식을 처리할 수 있음
  3. 실수와 복소수 객체의 덧셈 연산자
    • 좌측 피 연산자가 실수이므로 Complex2 클래스의 멤버로 연산자를 정의할 수 없음
      • 클래스에 속하지 않는 외부의 별도 연산자로 정의함
    • 수식

      1
      
        10.0 + complex2Obj
      
      • 10.0
        • r
      • complex2Obj
        • c
    • 초기 구현 시도 (오류 발생)

      1
      2
      3
      4
      5
      
        Complex2 operator + (double r, const Complex2 &c)
        {
          // 오류! private 멤버 사용 불가
          return Complex2(r + c.rPart, c.iPart);
        }
      
  • 해법 1 - Complex2에 private멤버를 액세스할 수 있는 멤버 함수 정의

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      class Complex2 {
        double rPart, iPart;
      public:
        double real() const { return rPart; } // 실수 부의 값 반환
        double imag() const { return iPart; } // 허수 부의 값 반환
      };
        
      Complex2 operator + (double r, const Complex2 &c)
      {
        return Complex2(r + c.real(), c.imag());
      }
    
  • 해법 2- Complex2에서 다중 정의된 연산자를 friend로 선언

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      class Complex2 {
        double rPart, iPart;
      public:
        friend Complex2 operator + (double r, const Complex2& c);
      };
        
      Complex2 operator + (double r, const Complex2 &c)
      {
        return Complex2(r + c.rPart, c.iPart);
      }
    
  1. 복소수 객체의 복합 대입 연산자
    • 수식

      1
      
        complex2Obj1 += complex2Obj2
      
    • 구현

      1
      2
      3
      4
      5
      6
      
        Complex2& Complex2::operator += (const Complex2 &c)
        {
          rPart += c.rPart;
          iPart += c.iPart;
          return *this;
        }
      

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

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

  • 스트림 출력 연산자를 정의할 위치

    1
    2
    
      Complex2 c(1.0, 2.0);
      cout << c;
    
    • cout << c;
      • 좌측 피 연산자인 cout이 Complex2의 객체가 아니며, cout이 속한 ostream클래스를 일반 프로그래머가 수정할 수 없음
    • 클래스에 속하지 않는 외부의 별도 연산자로 정의함
    • <<연산자가 Complex2 객체의 private멤버를 액세스할 수 있게 friend로 지정
  • 스트림 출력 연산자가 반환할 값

    • cout을 통한 연속적인 출력 문장

      1
      
        cout << "변수 a에 저장된 값 " << a; // 출력 후 cout 반환
      
      • 반환 된 cout에 a를 출력

        1
        
          cout << a;
        

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

  • 클래스 내 friend선언

    1
    2
    3
    
      class Complex2 {
        friend ostream& operator<<(ostream &os, const Complex2 &c);
      };
    
  • 연산자 구현

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      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;
      }
    
  • main함수 예제

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      #include <iostream>
      #include "Complex2.h"
      using namespace std;
        
      int main()
      {
        Complex2 a(10, 20);
        Complex2 b(5, -3);
        cout << a << " + " << b << " = " << a + b << endl;
        return 0;
      }
        
      // (10+j20) + (5-j3) = (15+j17)
    



연습 문제


  1. 연산자를 다중 정의하는 것에 대한 올바른 설명은?

    a. 연산자의 의미를 임의로 바꾸지 않는다.

    • 연산자 다중 정의를 통해 연산자의 우선 순위 피 연산자의 수 등 연산자 사용 방법을 바꿀 수 없음
    • 연산자의 다중 정의를 하더라도 그 의미가 유지되게 하는 것이 좋은 방법임
  2. 위 지문의 클래스 선언문에서 ++ 연산자를 다중 정의하기 위해 ㈀에 넣을 적절한 내용은?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     class ClassA {
      int value;
     public:
      ClassA(int x) : value(x) { }
      ClassA // ㈀
      {
         ClassA tmp(*this);
         ++value;
         return tmp;
       }
     };
    

    a. operator++ (int)

    • ++ 연산자를 다중 정의하는 것임
    • 기존 객체의 내용을 복사한 후 이를 반환하는 것으로 보아 후위 표기법을 정의한 것으로 보는 것이 타당하므로, 후위 표기 단항 연산자 ++를 정의하는 문장이 사용 됨
  3. 어떤 클래스 ClassB에 위 지문과 같은 연산자를 정의하였다. 이에 대한 올바른 설명은? (x와 y는 이 클래스의 객체라고 가정함)

    1
    2
    3
    
     ClassB ClassB::operator+(const ClassB& a) const {
       // ㈀
     }
    

    a. x+y 라는 수식이 사용되었을 때 ㈀에서 *this는 x이다.

    • 클래스의 멤버로 다중 정의된 + 연산자이므로 객체 자신(즉, ㈀ 영역에서 *this)이 좌측 피 연산자, 인수로 전달된 a가 우측 피 연산자임
  4. 위 지문과 같이 ClassA의 private멤버를 자유롭게 사용하는 함수 f를 정의할 수 있도록 하려면 ㈀에 어떤 단어가 필요한가?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     class ClassA {
       int x, y;
     	// ㈀ int f(const ClassA& a);
     };
        
     int f(const ClassA& a)
     {
       return a.x + a.y;
     }
    

    a. friend

    • 함수 f가 ClassA의 private멤버를 액세스할 수 있으려면 함수 f를 ClassA의 친구 함수로 선언해야 함



정리 하기


  • 사용자가 선언한 클래스에서 C++에 정의된 연산자를 의미와 목적에 맞게 다중 정의하여 사용할 수 있음
  • 멤버 선택 연산자, 멤버에 대한 포인터 연산자, 유효 범위 결정 연산자, 조건 연산자는 다중 정의할 수 없음
  • 단항 연산자인 ++-연산자는 전위 표기와 후위 표기 각각에 대해 다중 정의할 수 있음
  • 이항 연산자의 다중 정의는 좌측 피 연산자에 해당되는 클래스에서 하며, 이때 this가 좌측 피 연산자, 형식 매개 변수가 우측 피 연산자에 해당 됨
  • 특정 클래스에 속하지 않은 연산자 다중 정의를 할 경우 피 연산자가 모두 매개 변수를 통해 전달 되도록 연산자 다중 정의를 함
  • 필요하다면 피 연산자의 private멤버를 자유롭게 사용할 수 있도록 friend지정을 함

[C++ 프로그래밍] 7강 - 클래스와 객체

[C++ 프로그래밍] 9강 - 연산자 다중 정의