Unary operator, as Member Function
◆ 단항(Unary) 연산자의 멤버 함수로의 선언 ( ++ , -- , -,!)
● 선언 형태
Point operator-() const;
Point operator++(); // pre-increment
Point operator++(int); // post-increment
bool operator!() const;
● 사용 예시
Point p1{ 10,20 };
Point p2 = -p1; // p1.operator-();
p2 = ++p1; // p1.operator++();
p2 = p1++; // p1.operator++(int);
✅ 단항 연산자 오버로딩 정리
🔹 단항 연산자란?
단항(Unary) 연산자는 피연산자가 하나만 필요한 연산자를 말한다.
대표적인 예시는 다음과 같다:
연산자 | 의미 |
-a | 부호 반전 (음수로 바꾸기) |
++a, a++ | 증가 |
--a, a-- | 감소 |
!a | 논리 부정 (true ↔ false) |
🔹 단항 vs 이항 연산자의 차이
int a = 10;
int b = 5;
표현식 | 의미 | 연산자 종류 |
-a | a의 부호를 반전시켜 -10으로 만듦 | 단항 연산자 |
a - b | a에서 b를 뺌 → 5 | 이항 연산자 |
-는 같은 기호지만, 피연산자 개수에 따라 동작이 달라지는 연산자다.
단항/이항 모두에서 사용 가능하지만
오버로딩 방식은 다르다.
🔹 단항 연산자의 오버로딩
클래스에서 단항 연산자를 오버로딩하려면 피연산자 하나만 있는 형태로 멤버 함수를 정의해야 한다.
예시: -p1 (부호 반전)
class Point {
private:
int xPos;
int yPos;
public:
Point(int x = 0, int y = 0) : xPos{x}, yPos{y} {}
// 단항 - 연산자 오버로딩
Point operator-() const {
return Point{-xPos, -yPos};
}
void Print() const {
std::cout << "(" << xPos << ", " << yPos << ")\n";
}
};
int main() {
Point p1{3, -5};
Point p2 = -p1; // p1.operator-()
p2.Print(); // (-3, 5)
}
🔹 단항 vs 이항 오버로딩 구조 차이
연산자 형태 | 함수 시그니처 | 인자 |
-p1 | Point operator-() const | 없음 |
p1 + p2 | Point operator+(const Point& rhs) const | rhs 1개 |
단항 연산자는 인자가 없고, 이항 연산자는 인자가 1개라는 점이 큰 차이이다.
🔹 전위/후위 증감자 차이 (간단 정리)
형태 | 시그니처 | 특징 |
++a (전위) | Point& operator++() | 값을 먼저 증가시킴 |
a++ (후위) | Point operator++(int) | 값을 사용한 뒤 증가 (더미 int 매개변수로 구분) |
✅요약 문장
단항 연산자는 피연산자가 하나인 연산자로, -, ++, --,! 등이 있다.
클래스에서 이를 오버로딩하려면 멤버 함수로 operator-() 같은 함수를 정의하면 된다.
단항 연산자 오버로딩은 인자가 없으며, 호출 시 obj.operator-() 형태로 해석된다.
🔹 단항 - 연산자 오버로딩
Point 클래스에서 단항 - 연산자(부호 반전)를 사용하려면,
해당 연산자에 대한 오버로딩을 직접 구현해야 한다.
Point p3 = -p1; // 실제로는 p1.operator-() 호출
위 코드는 컴파일 시 다음처럼 해석된다:
Point p3 = p1.operator-();
하지만 operator-() 함수가 정의되어 있지 않다면 컴파일러는 에러를 발생시킨다.
🔹 오버로딩 함수 정의
class Point {
private:
int xPos;
int yPos;
public:
// 단항 - 연산자 오버로딩
Point operator-() const {
return Point{-xPos, -yPos};
}
};
- operator-()는 인자를 받지 않는 단항 연산자다.
- 반환형은 Point 객체다:
왜냐하면 -p1의 결과를 다른 Point 객체(p3)에 저장하려면
새로운 객체를 반환해야 하기 때문이다. - const 멤버 함수인 이유는:
- p1 객체의 멤버 변수는 변경되지 않아야 하며
- const Point p1 객체에서도 호출 가능해야 하기 때문이다.
🔹 const의 의미 요약
- const Point p1 → 읽기 전용 객체
- const 멤버 함수만 호출 가능
→ operator-()에 const가 없으면 호출 자체가 불가능
📌 주의: const 멤버 함수는 const가 아닌 객체에서도 호출 가능하다.
반대는 불가능하다.
🔹 참고: 다른 단항 연산자의 오버로딩 시그니처
연산자 | 함수 시그니처 예시 |
++ (전위) | Point& operator++(); |
-- (전위) | Point& operator--(); |
! (논리 부정) | bool operator!() const; |
- (부호 반전) | Point operator-() const; |
단항 연산자는 대부분 인자가 없고, const 멤버 함수로 만드는 것이 일반적이다.
✅ 2. 최종 요약 문장
단항 - 연산자는 클래스 내부에서 operator-() 멤버 함수로 오버로딩할 수 있다.
이 함수는 인자를 받지 않으며, 보통 const 멤버 함수로 선언된다.
이는 연산 결과를 새로운 객체로 반환하면서 원본 객체는 변경하지 않기 때문이다.
또한, const 객체에서도 해당 연산자를 사용할 수 있게 하기 위해 const를 붙인다.
● 단항 - 연산자의 오버로딩 동작 방식의 이해
Point operator-() const;
Point p3 = -p2; // p2.operator-();
...
✅ 단항 연산자 오버로딩의 동작 방식
단항 연산자는 피연산자가 하나만 있는 연산자이다.
예를 들어, 아래 코드는 단항 연산자 -를 사용한 것이다:
Point p3 = -p2; // 내부적으로는 p2.operator-();
이때 컴파일러는 -p2 를 다음처럼 해석한다.
p2.operator-(); // 단항 - 연산자 호출
💡 즉, 연산자는 p2 한 개의 객체에만 적용하며, 추가적인 인자는 필요하지 않는다.
🔍 단항 연산자 vs 이항 연산자 – 구조 차이
구분 | 예시 | 내부 동작 방식 | 인자 |
단항 연산자 | -p2 | p2.operator-() | 없음 |
이항 연산자 | p1 + p2 | p1.operator+(p2) | 있음 (1개) |
단항 연산자는 멤버 함수에 인자가 없는 것이 핵심적인 차이다.
그 외의 구현 방식은 이항 연산자와 유사하다.
📌 핵심 요약 문장
단항 연산자는 피연산자가 하나뿐인 연산자로, operator-()처럼 인자 없이 오버로딩한다.
반면, 이항 연산자는 operator+(const Point& rhs)처럼 다른 객체를 인자로 받는다는 점에서 차이가 있다.
이 차이만 명확히 이해하면, 나머지 오버로딩 방식은 유사하게 작성할 수 있다.
중요한 Part이다.★★★★★
Limitation of Member Function
◆ 멤버 함수로 선언 시 한계점
● 교환 법칙이 성립하도록 구현이 불가능할 수 있다.
● 예를 들어 자료형이 다른 경우, 3(int) , p1(Point)를 가정해 보면,
● p1 * 3 은 함수 호출 가능, 3 * p1 은 함수 호출 불가!
Point p1{ 10,20 };
Point p2{ 30,40 };
Point p3 = p1 * 3; // p1.operator*(3)
Point p4 = 3 * p1; // 3.operator*(p1) <- ???
✅ 연산자 오버로딩: 멤버 함수 방식의 한계와 해결책
🔹 연산자 오버로딩 방식: 2가지
C++에서 연산자 오버로딩은 두 가지 방식으로 구현할 수 있다:
- 멤버 함수로 오버로딩
- 전역(비멤버) 함수로 오버로딩
이 중 멤버 함수 방식은 직관적이지만, 명확한 한계점이 존재한다.
대표적인 예가 바로 교환 법칙(commutativity) 문제다.
🔹 교환 법칙이 깨지는 사례
Point p1{10, 20};
Point p3 = p1 * 3; // 가능: p1.operator*(3)
Point p4 = 3 * p1; // 에러: 3.operator*(p1) → 불가능
- p1 * 3은 p1이 클래스 객체이므로 operator*(int) 멤버 함수가 호출되어 정상 작동한다.
- 하지만 3 * p1은 왼쪽 피연산자가 int 타입이기 때문에
3.operator*(p1) 형태로 해석되고, 이는 문법적으로 불가능하다.
→ 리터럴(3)은 멤버 함수가 없기 때문!
즉, 피연산자의 순서가 바뀌면 작동하지 않는다는 것이 멤버 함수 방식의 근본적인 한계다.
🔹 정리: 멤버 함수 방식의 한계
문제 | 설명 |
교환 법칙 불가 | p1 * 3은 되지만, 3 * p1은 안 됨 |
리터럴은 멤버 함수 없음 | 3.operator*(p1) 같은 해석은 불가능 |
표현 제약 | 항상 클래스 객체가 왼쪽 피연산자여야 함 |
✅ 해결 방법: 전역 함수로 연산자 오버로딩
이 문제를 해결하려면 전역 함수(non-member) 방식으로 오버로딩을 구현하면 된다.
전역 함수는 왼쪽 피연산자도 우리가 제어 가능하므로, 3 * p1처럼 좌우 순서를 자유롭게 구현할 수 있다.
📌 최종 요약 문장
멤버 함수로 연산자 오버로딩을 구현하면, 클래스 객체가 왼쪽에 있을 때만 연산이 가능하다.
반면 3 * p1처럼 상수가 왼쪽인 경우는 멤버 함수 방식으로 구현할 수 없으며,
이를 해결하기 위해서는 전역 함수 기반 오버로딩이 필요하다.
Operator Overloading
◆ 연산자 오버로딩 : 클래스에 대한 연산자의 적용 방식을 사용자가 직접 오버로딩하여 구현할 수 있다.
◆ 멤버 한수인 연산자 오버로딩 : 클래스의 멤버함수로 operatorX()라는 이름을 갖는 함수를 구현하여 연산자를 오버로딩 할 수 있다. 이때 이항 연산자의 경우 우측 피연산자는 인자로 넘어온다.
◆ 전역 함수인 연산자 오버로딩
◆ 스트림 삽입 및 추출 연산자 오버로딩
◆ 대입 연산자 오버로딩
◆ 첨자 연상자 오버로딩
https://youtu.be/SJlIg2LJ7NE?si=-lMTfm3yGU4xkzH6
다음 강의 전역 함수 방식으로 오버로딩 구현이 기대된다.
'C++ > C++ : Study' 카테고리의 다른 글
8. 연산자 오버로딩 (5) - Ostream 객체 (0) | 2025.06.16 |
---|---|
8. 연산자 오버로딩 (4) - 전역 함수로의 구현 (3) | 2025.06.15 |
8. 연산자 오버로딩 (2) - 이항 연산자 오버로딩 멤버 함수로서의 구현 (0) | 2025.06.09 |
8. 연산자 오버로딩 (1) - 소개 (3) | 2025.06.08 |
다형성(6) - 인터페이스 (2) | 2025.05.30 |