|7.2| 연산자 중복 (operator overloading)
: C++의 다형성(polymorphism)이다. 피연산자에 따라 서로 다른 연산을 하도록 동일한 연산자를 중복해서 작성하는 것.
● 연산자 중복의 특징
1) C++에 원래 있는 연산자만 중복 가능
2) 정수/실수 가 아닌 객체나 값
3) 연산자 중복으로 연산자의 우선 순위를 바꿀 수 없음
4) 안되는 연산자도 있음
● 연산자 함수 작성 방법
1) 클래스의 멤버 함수로 구현
2) 외부 함수로 구현하고 클래스의 프랜드 함수로 선언
리턴타입 operator 연산자(parameter);
1) 클래스의 멤버 함수로 선언되는 경우
| class Color { ... Color operator + (Color op2); /* 왼쪽 피연산자는 객체 자신이고 오른쪽 피연산자가 op2에 전달*/ bool operator == (Color op2); /*왼쪽 피연산자는 객체 자신 오른쪽 피연산자가 op2에 전달*/ }; | cs |
=> + 나 == 의 오른쪽 피연산자만 매개 변수 op2에 전달되고, 왼쪽 피연산자는 객체 자신이므로 매개 변수에 전달되지 않는다.
2) 외부 함수로 구현하고 클래스에 프렌드 함수로 선언하는 경우
| Color operator + (Color op1, Color op2); // 외부 전역 함수 bool operator == (Color op1, Color op2); // 외부 전역 함수 ... class Color { ... friend Color operator + (Color op1, Color op2); // 프렌드 선언 friend bool operator == (Color op1, Color op2); // 프렌드 선언 }; | cs |
|7.3| 이항 연산자 중복
: 예시를 통해 연산자 중복에 대해 제대로 이해해보자!
| class Power { // 에너지를 표현하는 파워 클래스 int kick; // 킥 파워 int punch; // 펀치 파워 public: Power(int kick = 0, int punch = 0){ this->kick = kick; this->punch = punch; } }; | cs |
● 1) + 연산자 중복
※ 우선 이 예제에서 +의 의미를 결정해야 하는데, 이 것은 전적으로 개발자의 몫이다.
여기서는 +연산을 두 객체의 kick과 punch를 더하는 것으로 정의한다.
| Power a(3, 5), b(4, 6), c; c = a + b; // 두 객체 a, b의 파워를 더하는 + 연산 | cs |
※ 컴파일러는 a + b 의 연산이 C++의 기본 더하기로 처리될 수 없음을 판단한다. 따라서 Power 클래스에 Power 객체를 더하는
+ 연산자 함수가 새로 선언되어 있는지 찾는다.
이를 위해 컴파일러는 a + b를 다음과 같이 변형한다.
※ 이 식은 다음과 같이 Power 객체 a의 멤버 함수 operator+()를 호출하며, b를 매개 변수로 넘겨주는 함수 호출이다.
우리는 이 호출이 성공할 수 있도록 다음과 같이 operator+()함수를 Power클래스의 멤버 함수로 선언한다.
| class Power() { Power operator+ (Power op2); // + 연산자 함수 선언 } | cs |
※ operator+() 함수는 리턴 타입을 Power로 하고, 더한 결과인 새로운 Power 객체를 리턴한다.
※ 위의 operator+() 함수의 구현은 다음과 같다.
| Power Power::operator+(Power op2) { Power tmp; tmp.kick = this->kick + op2.kick; tmp.punch = this - punch + op2.punch; return tmp; } | cs |
=> 이 연산자 함수는 더한 결과인 tmp 객체를 리턴한다.
< 예시 구현 >
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 30 31 32 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); Power operator+ (Power op2); // + 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } Power Power::operator+(Power op2) { Power tmp; // 임시 객체 생성 tmp.kick = this->kick + op2.kick; // kick 더하기 tmp.punch = this->punch + op2.punch; // punch 더하기 return tmp; // 더한 결과 리턴 } int main() { Power a(3,5), b(4,6), c; c = a + b; // 파워 객체 + 연산 a.show(); b.show(); c.show(); } | cs |
● 2) == 연산자 중복
: a == b 가 true인 경우 각각의 kick과 punch가 같은 경우로 정의한다. 따라서 operator==()의 리턴 타입은 bool이다.
※ 컴파일러는 a==b의 식을 다음과 같이 변형하여 Power 클래스의 멤버로 작성된 operator==()함수를 찾는다.
※ 이 식은 Power 객체 a의 operator==() 연산자 함수를 호출하고 b를 매개 변수로 넘긴다.
< 예시 구현 >
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 30 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); bool operator== (Power op2); // == 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } bool Power::operator==(Power op2) { if(kick==op2.kick && punch==op2.punch) return true; else return false; } int main() { Power a(3,5), b(3,5); // 2 개의 동일한 파워 객체 생성 a.show(); b.show(); if(a == b) cout << "두 파워가 같다." << endl; else cout << "두 파워가 같지 않다." << endl; } | cs |
● 3) += 연산자 중복
: 언뜻 보면 아무 값도 리턴할 필요가 없어 void로 하면 될 것 같지만
c = a += b; 처럼 사용될 수도 있다.
(이 문장은 a += b를 먼저 계산하고 결과를 c에 치환하므로, a와 b를 더한 a를 리턴하도록 작성되어야 한다.)
※ 컴파일러는 a+=b;의 식을 다음과 같이 변형한다.
< 예시 구현 >
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 30 31 32 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); Power& operator+= (Power op2); // += 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } Power& Power::operator+=(Power op2) { kick = kick + op2.kick; // kick 더하기 punch = punch + op2.punch; // punch 더하기 return *this; // 합한 결과 리턴 } int main() { Power a(3,5), b(4,6), c; a.show(); b.show(); c = a += b; // 파워 객체 더하기 a.show(); c.show(); } | cs |
※ this를 이용하여 return한다는 점에 주목하자.
● 4) + 연산자 b = a + 2 ;
※ 컴파일러는 우선 a + 2 를 다음과 같이 변형할 것이다.
위의 + 연산자 중복 예제에서 파라미터를 int op2로 바꿔주면 되겠다.
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 30 31 32 33 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); Power operator+ (int op2); // + 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } Power Power::operator+(int op2) { Power tmp; // 임시 객체 생성 tmp.kick = kick + op2; // kick에 op2 더하기 tmp.punch = punch + op2; // punch에 op2 더하기 return tmp; // 임시 객체 리턴 } int main() { Power a(3,5), b; a.show(); b.show(); b = a + 2; // 파워 객체와 정수 더하기 a.show(); b.show(); } | cs |
|7.4| 단항 연산자 중복
: 피연산자가 하나인 단항 연산자의 연산자 함수
1) 전위 연산자 : !op, ~op, ++op, --op
2) 후위 연산자 ; op++, op--
● 전위 ++ 연산자 중복
: 객체 a의 멤버들의 값을 각각 1씩 증가시킨 후 변경된 객체 a를 리턴시킨다.
※ 컴파일러는 ++a 식을 다음과 같이 변형한다
< 예시 구현 >
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 30 31 32 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); Power operator++ (); // 전위 ++ 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } Power Power::operator++() { kick++; punch++; return *this; // 변경된 객체 자신(객체 a)의 참조 리턴 } int main() { Power a(3,5), b; a.show(); b.show(); b = ++a; // 전위 ++ 연산자 사용 a.show(); b.show(); } | cs |
< 예시 : ! 연산자 작성 >
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 30 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); bool operator! (); // ! 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } bool Power::operator!() { if(kick == 0 && punch == 0) return true; else return false; } int main() { Power a(0,0), b(5,5); if(!a) cout << "a의 파워가 0이다." << endl; // ! 연산자 호출 else cout << "a의 파워가 0이 아니다." << endl; if(!b) cout << "b의 파워가 0이다." << endl; // ! 연산자 호출 else cout << "b의 파워가 0이 아니다." << endl; } | cs |
● 후위 ++ 연산자 중복
: a++는 증가 이전의 객체 a를 리턴하는 것으로 정의한다.
※ a++ vs ++a
| Power operator++(); // ++a Power operator++(int x); // a++ | cs |
※ 후위 연산자 함수에서 x에는 의미 없는 값이 전달되므로 무시해도 된다.
※ 컴파일러는 a++식을 다음과 같이 변형한다.
| a .++(임의의 정수); // 임의의 정수가 operator++(int)의 매개 변수로 넘겨진다. | cs |
< 예시 구현 >
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 30 31 32 33 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); Power operator++ (int x); // 후위 ++ 연산자 함수 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } Power Power::operator++(int x) { Power tmp = *this; // 증가 이전 객체 상태를 저장 kick++; punch++; return tmp; // 증가 이전 객체 상태 리턴 } int main() { Power a(3,5), b; a.show(); b.show(); b = a++; // 후위 ++ 연산자 사용 a.show(); // a의 파워는 1 증가됨 b.show(); // b는 a가 증가되기 이전 상태를 가짐 } | cs |
● (다시) 전위 연산자 vs 후위 연산자 함수 비교
전위 연산자
| Power Power::operator++() { kick++; punch++; return *this; // 변경된 객체 자신 리턴 } | cs |
후위 연산자
| Power Power::operator++(int x) { Power tmp = *this; // 증가 이전의 객체 상태 저장 kick++; punch++; return tmp; // 증가 이전의 객체 리턴 } | cs |
|7.5| 프렌드를 이용한 연산자 중복
: 지금까지는 ' 1) 클래스의 멤버 함수로 구현 의 방법' 으로 연산자 함수를 작성하였다.
이번에는 ' 2) 외부 함수로 구현하고 클래스의 프랜드 함수로 선언 ' 으로 작성한다 (외부 전역 함수로 작성)
● 2 + a를 위한 + 연산자 함수 작성
| Power a(3, 4), b; b = 2 + a; | cs |
위의 내용에 따르면, 컴파일러는 2 + a 의 연산을 다음과 같이 변형하여 operator+() 함수를 호출하려고 할 것이다.
근데 2가 객체가 아니네??
-> 컴파일러는 다음과 같은 식으로 변환한다.
=> 사실 컴파일러에게 두 개의 옵션이 있는 셈이다.
위의 식이 성공적이기 위해서는 operator+()함수를 Power클래스의 외부 함수로 밖에 구현할 수 없다.
| Power operator+ (int op1, Power op2) { Power tmp; tmp.kick = op1 + op2.kick; tmp.punch = op1 + op2.punch; return tmp; } | cs |
● 외부 연산자 함수의 프렌드 선언
: 근데 문제가 있음. 함수 내에서 Power의 private 멤버인 kick과 punch를 자유롭게 접근하기 떄문에 연산자 함수에 컴파일 오류가 생길 것이다.
그럼, kick과 punch를 public으로 선언하면 어떨까?
-> 근데 그럼 연산자 함수를 쓰려고 Power 클래스의 캡슐화의 원칙을 무너뜨리게 되는 치명적인 판단 미스가 된다!
===> friend를 사용하면 깔끔하게 해결된다. 외부에 구현된 operator+(int, Power)의 연산자 함수를 Power 클래스에 프렌드로 초대한다.
| class Power { int kick; int punch; public: ... friend Power operator+(int op1, Power op2); // 프렌드 선언 }; Power operator+(int op1, Power op2) { // 외부 함수로 구현된 연산자 함수 ... } | cs |
< 예시 구현 >
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 30 31 32 33 | #include <iostream> using namespace std; class Power { int kick; int punch; public: Power(int kick=0, int punch=0) { this->kick = kick; this->punch = punch; } void show(); friend Power operator+(int op1, Power op2); // 프렌드 선언 }; void Power::show() { cout << "kick=" << kick << ',' << "punch=" << punch << endl; } Power operator+(int op1, Power op2) { Power tmp; // 임시 객체 생성 tmp.kick = op1 + op2.kick; // kick 더하기 tmp.punch = op1 + op2.punch; // punch 더하기 return tmp; // 임시 객체 리턴 } int main() { Power a(3,5), b; a.show(); b.show(); b = 2 + a; // 파워 객체 더하기 연산 a.show(); b.show(); } | cs |
● a + b 를 외부 프렌드 함수로 작성
| Power operator+(Power op1, Power op2) { Power tmp; tmp.kick. = op1.kick + op2.kick; tmp.punch = op1.punch + op2.punch; return tmp; } | cs |
를 외부 전역 함수로 구현하고, Power 클래스 내에
| friend Power operator+(Power op1, Power op2); | cs |
를 구현하면 되겠다.
※ 동일한 연산자 함수를 멤버 함수와 프렌드 함수로 동시에 구현할 수 없다.
● 단항 연산자 ++를 프렌드로 작성하기
전위 연산자
| Power operator++(Power& op) { op.kick++; op.punch++; return op; } | cs
|
후위 연산자
| Power operator++(Power& op, int x) { Power tmp = op; op.kick++; op.punch++; return tmp; } | cs |
클래스 내에
| friend Power operator++(Power& op); friend Power operator++(Power& op, int x); | cs |
※ 참조 매개 변수를 사용하지 않으면 복사본이 전달되므로 op 객체의 kick과 punch의 값이 변하지 않는다.