학습 개요
- 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;
11 11 b = a++;
11 10 b = --a;
9 9 b = a--;
9 10
전위 표기법 단항 연산자의 다중 정의
다중 정의의 형식
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
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); }
- 복소수 객체와 실수의 덧셈 연산자
수식
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 객체 간 덧셈 연산자를 이용하여 수식을 처리할 수 있음
- 실수와 복소수 객체의 덧셈 연산자
- 좌측 피 연산자가 실수이므로 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); }
- 좌측 피 연산자가 실수이므로 Complex2 클래스의 멤버로 연산자를 정의할 수 없음
해법 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
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)
연습 문제
연산자를 다중 정의하는 것에 대한 올바른 설명은?
a. 연산자의 의미를 임의로 바꾸지 않는다.
- 연산자 다중 정의를 통해 연산자의 우선 순위 피 연산자의 수 등 연산자 사용 방법을 바꿀 수 없음
- 연산자의 다중 정의를 하더라도 그 의미가 유지되게 하는 것이 좋은 방법임
위 지문의 클래스 선언문에서 ++ 연산자를 다중 정의하기 위해 ㈀에 넣을 적절한 내용은?
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)
- ++ 연산자를 다중 정의하는 것임
- 기존 객체의 내용을 복사한 후 이를 반환하는 것으로 보아 후위 표기법을 정의한 것으로 보는 것이 타당하므로, 후위 표기 단항 연산자 ++를 정의하는 문장이 사용 됨
어떤 클래스 ClassB에 위 지문과 같은 연산자를 정의하였다. 이에 대한 올바른 설명은? (x와 y는 이 클래스의 객체라고 가정함)
1 2 3
ClassB ClassB::operator+(const ClassB& a) const { // ㈀ }
a. x+y 라는 수식이 사용되었을 때 ㈀에서
*this
는 x이다.- 클래스의 멤버로 다중 정의된 + 연산자이므로 객체 자신(즉, ㈀ 영역에서
*this
)이 좌측 피 연산자, 인수로 전달된 a가 우측 피 연산자임
- 클래스의 멤버로 다중 정의된 + 연산자이므로 객체 자신(즉, ㈀ 영역에서
위 지문과 같이 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
지정을 함