학습 개요
- 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
complex2Obj2c
구현
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.0r
complex2Objc
초기 구현 시도 (오류 발생)
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지정을 함