|7.2| 연산자 중복 (operator overloading)


:  C++의 다형성(polymorphism)이다.  피연산자에 따라 서로 다른 연산을 하도록 동일한 연산자를 중복해서 작성하는 것.



● 연산자 중복의 특징


1) C++에 원래 있는 연산자만 중복 가능

2) 정수/실수 가 아닌 객체나 값

3) 연산자 중복으로 연산자의 우선 순위를 바꿀 수 없음

4) 안되는 연산자도 있음



● 연산자 함수 작성 방법


1) 클래스의 멤버 함수로 구현

2) 외부 함수로 구현하고 클래스의 프랜드 함수로 선언



리턴타입 operator 연산자(parameter);



1) 클래스의 멤버 함수로 선언되는 경우


1
2
3
4
5
6
7
class Color {
    ...
        Color operator + (Color op2); /* 왼쪽 피연산자는 객체 자신이고
                                      오른쪽 피연산자가 op2에 전달*/
    bool operator == (Color op2); /*왼쪽 피연산자는 객체 자신
                                  오른쪽 피연산자가 op2에 전달*/
};
cs



=> + 나 == 의 오른쪽 피연산자만 매개 변수 op2에 전달되고, 왼쪽 피연산자는 객체 자신이므로 매개 변수에 전달되지 않는다.



2) 외부 함수로 구현하고 클래스에 프렌드 함수로 선언하는 경우


1
2
3
4
5
6
7
8
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| 이항 연산자 중복


: 예시를 통해 연산자 중복에 대해 제대로 이해해보자!


1
2
3
4
5
6
7
8
9
10
11
class Power { // 에너지를 표현하는 파워 클래스
    int kick;    // 킥 파워
    int punch;    // 펀치 파워
 
public:
    Power(int kick = 0int punch = 0){
        this->kick = kick;
        this->punch = punch;
    }
 
};
cs


● 1) + 연산자 중복


※ 우선 이 예제에서 +의 의미를 결정해야 하는데, 이 것은 전적으로 개발자의 몫이다.  
   여기서는 +연산을 두 객체의 kick과 punch를 더하는 것으로 정의한다.

1
2
3
Power a(35), b(46), c;
 
= a + b; // 두 객체 a, b의 파워를 더하는 + 연산
cs



※ 컴파일러는 a + b 의 연산이 C++의 기본 더하기로 처리될 수 없음을 판단한다. 따라서 Power 클래스에 Power 객체를 더하는

   + 연산자 함수가 새로 선언되어 있는지 찾는다.

   이를 위해 컴파일러는 a + b를 다음과 같이 변형한다.


1
a . + ( b );
cs


※ 이 식은 다음과 같이 Power 객체 a의 멤버 함수 operator+()를 호출하며, b를 매개 변수로 넘겨주는 함수 호출이다.

우리는 이 호출이 성공할 수 있도록 다음과 같이 operator+()함수를 Power클래스의 멤버 함수로 선언한다.


1
2
3
class Power() {
    Power operator+ (Power op2); // + 연산자 함수 선언
}
cs


※ operator+() 함수는 리턴 타입을 Power로 하고, 더한 결과인 새로운 Power 객체를 리턴한다.


※ 위의 operator+() 함수의 구현은 다음과 같다.


1
2
3
4
5
6
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=0int 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==()함수를 찾는다.


1
a. == (b)
cs


※ 이 식은 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=0int 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(== bcout << "두 파워가 같다." << endl;
    else cout << "두 파워가 같지 않다." << endl;
}
cs



● 3) += 연산자 중복


: 언뜻 보면 아무 값도 리턴할 필요가 없어 void로 하면 될 것 같지만 

  c = a += b; 처럼 사용될 수도 있다.

  (이 문장은 a += b를 먼저 계산하고 결과를 c에 치환하므로, a와 b를 더한 a를 리턴하도록 작성되어야 한다.)


※ 컴파일러는 a+=b;의 식을 다음과 같이 변형한다.


1
a . += ( b ) ;
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
#include <iostream>
using namespace std;
 
class Power {
    int kick; 
    int punch; 
public:
    Power(int kick=0int 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 를 다음과 같이 변형할 것이다.


1
a . + ( 2 )
cs


위의 + 연산자 중복 예제에서 파라미터를 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=0int 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--

● 전위 ++ 연산자 중복

1
2
Power a(3,5), b;
= ++a;
cs


: 객체 a의 멤버들의 값을 각각 1씩 증가시킨 후 변경된 객체 a를 리턴시킨다.


※ 컴파일러는 ++a 식을 다음과 같이 변형한다


1
a . ++ ( );
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
#include <iostream>
using namespace std;
 
class Power {
    int kick;
    int punch; 
public:
    Power(int kick=0int 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();
    = ++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=0int 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 == 0return true;
    else return false;
}
 
int main() {
    Power a(0,0), b(5,5);
    if(!acout << "a의 파워가 0이다."  << endl// ! 연산자 호출
    else cout << "a의 파워가 0이 아니다." << endl;
    if(!bcout << "b의 파워가 0이다." << endl// ! 연산자 호출
    else cout << "b의 파워가 0이 아니다." << endl;
}
cs



● 후위 ++ 연산자 중복


: a++는 증가 이전의 객체 a를 리턴하는 것으로 정의한다.


※ a++ vs ++a


1
2
Power operator++();        // ++a
Power operator++(int x);    // a++
cs


※ 후위 연산자 함수에서 x에는 의미 없는 값이 전달되므로 무시해도 된다.


※ 컴파일러는 a++식을 다음과 같이 변형한다.


1
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=0int 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 후위 연산자 함수 비교



전위 연산자


1
2
3
4
5
Power Power::operator++() {
    kick++;
    punch++;
    return *this// 변경된 객체 자신 리턴
}
cs



후위 연산자


1
2
3
4
5
6
Power Power::operator++(int x) {
    Power tmp = *this// 증가 이전의 객체 상태 저장
    kick++;
    punch++;
    return tmp; // 증가 이전의 객체 리턴
}
cs





|7.5| 프렌드를 이용한 연산자 중복


: 지금까지는 ' 1) 클래스의 멤버 함수로 구현 의 방법' 으로 연산자 함수를 작성하였다.

이번에는 ' 2) 외부 함수로 구현하고 클래스의 프랜드 함수로 선언 ' 으로 작성한다 (외부 전역 함수로 작성)



● 2 + a를 위한 + 연산자 함수 작성


1
2
Power a(34), b;
= 2 + a;
cs


위의 내용에 따르면, 컴파일러는 2 + a 의 연산을 다음과 같이 변형하여 operator+() 함수를 호출하려고 할 것이다.


1
2 . + ( a )
cs


근데 2가 객체가 아니네??


-> 컴파일러는 다음과 같은 식으로 변환한다. 


1
+ ( 2 , a )
cs



=> 사실 컴파일러에게 두 개의 옵션이 있는 셈이다.


위의 식이 성공적이기 위해서는 operator+()함수를 Power클래스의 외부 함수로 밖에 구현할 수 없다.


1
2
3
4
5
6
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 클래스에 프렌드로 초대한다.



1
2
3
4
5
6
7
8
9
10
11
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=0int 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 를 외부 프렌드 함수로 작성


1
2
3
4
5
6
Power operator+(Power op1, Power op2) {
    Power tmp;
    tmp.kick. = op1.kick + op2.kick;
    tmp.punch = op1.punch + op2.punch;
    return tmp;
}
cs


를 외부 전역 함수로 구현하고, Power 클래스 내에


1
friend Power operator+(Power op1, Power op2); 
cs


를 구현하면 되겠다.


※ 동일한 연산자 함수를 멤버 함수와 프렌드 함수로 동시에 구현할 수 없다.




● 단항 연산자 ++를 프렌드로 작성하기



전위 연산자


1
2
3
4
5
Power operator++(Power& op) {
    op.kick++;
    op.punch++;
    return op;
}

cs



후위 연산자


1
2
3
4
5
6
Power operator++(Power& op, int x) {
    Power tmp = op;
    op.kick++;
    op.punch++;
    return tmp;
}
cs



클래스 내에


1
2
friend Power operator++(Power& op);
friend Power operator++(Power& op, int x);
cs


※ 참조 매개 변수를 사용하지 않으면 복사본이 전달되므로 op 객체의 kick과 punch의 값이 변하지 않는다.

'C++ > C++ 문법' 카테고리의 다른 글

#8-2 상속과 객체 포인터  (0) 2018.10.07
#8-1 상속  (0) 2018.10.07
#07-A 프렌드  (0) 2018.10.05
#6 함수 중복과 static 멤버  (0) 2018.10.05
#5-B 복사 생성자  (0) 2018.10.05