연산자
C++ 에서 연산자는 다양한 자료형에 대응하여 연산을 진행한다. 예를들어 int 형과 int 형을 더하면 각 숫자를 더하여 반환하고, 문자열과 문자열을 더하면 각 문자열을 이어서 반환한다. 기본 자료형이 아니라 클래스에 대해서도 다른 연산을 진행하는 경우가 있다. 예를 들어 std::cout 은 << 연산자를 이용하여 입력을 받고, 출력을 진행한다. 기본적으로 << 연산자는 비트 이동 연산자인데 cout 에 적용하니 입력이 되는 것이다.
연산자 오버로딩은 클래스 내부에서 정의되어 위 예시처럼 사용될 수도 있고, 전역에서 정의되어 사용할 수도 있다. 그러나 전역에서 사용되려면 friend 를 알아야 제대로 사용할 수 있다.
클래스 내 연산자 오버로딩
연산자 오버로딩은 클래스 내에서 진행된다. 선언하는 문법은 다음과 같다.
return_datatype operator operator_name(parameter);
operator_name 에 연산자를 넣고, operator 키워드를 사용하는 것 말고는 기본적인 맴버 함수 선언과 같다. 매개변수(parameter) 사용은 연산자 선언이 실제 컴파일에서 어떻게 동작하는 지를 참고하면 알면 쉽게 설정할 수 있다.
이항 연산자의 경우, 예를 들어 a + b 라는 수식을 계산할 때는 컴파일러에 의해 a.+(b) 로 변형되어 계산된다. 즉 a 객체의 b 를 매개변수로 받는 + 라는 함수로 변형된다. 단항 연산자의 경우, 예를 들어 ! a 라 하면 a.!() 로 변형된다. 즉 매개변수가 없다. 단 전위, 후위 연산자 구분은 필요할텐데, 전위인 경우, 즉 ++a 와 같은 경우는 매개변수 없이, 후위, 즉 a++ 와 같은 경우는 매개변수로 임의의 int 를 가지는 형태로 선언된다.
단항 연산자를 예로 radius 을 멤버 변수로 갖는 Circle 클래스에서 전위 ++ 연산자를 다음과 같이 선언할 수 있다.
class Circle {
int radius;
public:
Circle(int a) {
radius = a;
}
Circle& operator++() {
radius++;
return *this;
}
};
전위 연산자기 때문에 매개변수 없이 선언되었고, 멤버 변수인 radius 를 증가시키고, 자기 자신을 참조로 반환하였다. 기본적으로 전위 ++ 연산자가 증가시킨 자신을 반환한다는 것을 반영하여 구현한 것이다. 전혀 다른 방식으로 연산자를 구현할 수도 있지만, 기본적으로 원래의 연산자가 가진 성질을 유지하는 것이 추후 해당 연산자를 사용할 때 실수를 줄이기 때문이다.
이항 연산자 + 를 구현해보자면 아래와 같이 구현할 수 있겠다. 이번에는 위 Circle 클래스를 재활용하겠다.
class Circle {
int radius;
public:
Circle(int a) {
radius = a;
}
Circle operator+(Circle circle) {
Circle temp(this->radius + circle.radius);
return temp;
}
};
입력받은 Circle 객체, 즉 뒤에 있는 객체의 반지름과 연산자가 적용되는 객체, 즉 앞에 있는 객체의 반지름을 더하여 새로운 객체를 만들고 그 객체를 반환하였다.
전역 연산자 오버로딩
전역으로 연산자 오버로딩을 하기 위해서는 기본 자료형에 대해서라면 상관없지만, 클래스의 객체를 매개변수로 사용한다면 friend 에 대한 이해(참고 링크)가 필요하다. 또한 컴파일러에 의해 연산자가 어떻게 변형되는지에 대한 이해도 필요하다.
클래스 내 연산자 오버로딩은 클래스에 대한 멤버 함수로 컴파일러가 변환하였다. 예를 들어 a + b 를 a.+(b) 로 컴파일러가 변환하였다. 전역 연산자 오버로딩은 연산자 그 자체를 함수로 변환한다. 즉 a + b 를 +(a, b) 와 같은 식으로 컴파일러가 변환하는 것이다. 따라서 매개변수로 멤버 변수에 대한 접근이 허용되지 않은 클래스의 객체가 있을 때 friend 선언이 필요하다.
전역 연산자 오버로딩이 필요한 경우가 있다. 특히 a + b 와 같은 이항 연산자를 계산할 때 앞 a 가 클래스 객체가 아니라 기본 자료형이라면, 즉 int, double 과 같은 자료형이라면 전역 연산자 오버로딩이 강제된다. 클래스 내 연산자 오버로딩으로 접근한다면 a.+(b) 와 같이 컴파일러가 변형하였을 때 a 가 클래스 객체가 아니기 때문에 오류가 발생하기 때문이다.
이항 연산자 + 를 아래와 같이 전역 연산자 오버로딩을 할 수 있다.
class Circle {
int radius;
public:
Circle(int a) {
radius = a;
}
friend Circle operator+(int a, Circle circle) {
Circle temp(a + circle.radius);
return temp;
}
};
연산자 오버로딩이 가능한 연산자 목록
연산자 | 원래 연산 유형 | 연산자 수 |
() | 함수 호출 | - |
[] | 배열 인덱스 | - |
-> | 포인터를 통한 멤버 접근 | 이항 |
->* | 포인터를 통한 멤버 포인터 접근 | 이항 |
++ | 증가 후위 | 단항 |
-- | 감소 후위 | 단항 |
++ | 증가 전위 | 단항 |
-- | 감소 전위 | 단항 |
+ | 양수 | 단항 |
- | 음수 | 단항 |
! | 논리 NOT | 단항 |
~ | 비트 NOT | 단항 |
& | 주소 | 단항 |
* | 역참조 | 단항 |
(type) | 캐스트 | 단항 |
* | 곱하기 | 이항 |
/ | 나누기 | 이항 |
% | 나머지 | 이항 |
+ | 더하기 | 이항 |
- | 빼기 | 이항 |
<< | 비트 좌측 이동 | 이항 |
>> | 비트 우측 이동 | 이항 |
< | 관계 | 이항 |
> | 이항 | |
<= | 이항 | |
>= | 이항 | |
== | 동등 | 이항 |
!= | 부등 | 이항 |
& | 비트 AND | 이항 |
^ | 비트 XOR | 이항 |
| | 비트 OR | 이항 |
&& | 논리 AND | 이항 |
|| | 논리 OR | 이항 |
= | 단순 및 복합 대입 | 이항 |
+= | 이항 | |
-= | 이항 | |
*= | 이항 | |
/= | 이항 | |
%= | 이항 | |
&= | 이항 | |
|= | 이항 | |
^= | 이항 | |
<<= | 이항 | |
>>= | 이항 | |
, | 콤마 | 이항 |
delete | 동적 메모리 반환 | - |
new | 동적 메모리 할당 | - |
conversion operators | 형 변환 연산자 | 단항 |
참고로 아래 연산자들은 연산자 오버로딩이 불가능하다.
연산자 | 연산자 유형 |
. | 멤버 접근 |
.* | 멤버 포인터 접근 |
:: | 범위 확인 |
? : | 조건 |
# | 전처기리 변환 |
## | 전처리기 연결 |
'Language > C & C++' 카테고리의 다른 글
[C++] 업캐스팅(upcasting) 및 다운캐스팅(downcasting) (0) | 2024.11.13 |
---|---|
[C++] 클래스 상속(inheritance) 및 다중 상속 (0) | 2024.11.01 |
[C++] 프렌드(friend)를 통한 클래스 멤버 접근 (0) | 2024.10.27 |
[C/C++] 헤더파일 분할 작성과 헤더파일 중복 선언 방지 (0) | 2024.10.13 |
[C++] 얕은 복사(shallow copy)와 깊은 복사(deep copy) 그리고 복사 생성자 (0) | 2024.10.13 |