반환타입 operator연산자 (매개변수)
{
// 로직
}
+, -, *, << 등)가 사용자 정의 자료형(클래스나 구조체)에 대해서도 동작할 수 있게 하는 방법class Point
{
private:
int x, y;
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
// + 연산자 오버로딩
Point operator+(const Point& other)
{
return Point(this->x + other.x, this->y + other.y);
}
};
int main
{
Point A(1, 2);
Point B(4, 3);
Point C;
C = A + B; // A.operator+(B)가 호출되어 오버로딩한 함수를 사용
}
새로운 연산자(@나 **)를 만들 수는 없음
피연산자 개수 변경 불가: +는 무조건 2개, !는 1개가 필요
연산자 우선순위 유지: *가 +보다 먼저 계산되는 규칙은 변하지 않음
오버로딩 불가능한 연산자
. (멤버 선택)
.* (멤버 포인터 선택)
:: (범위 지정)
? : (삼항 연산자)
sizeof, typeid
단순하고 직관적인 기능에 맞게만 사용하기를 추천
전위: Point& operator++()
본인인 L-value를 반환해야하므로, 참조자로 반환
후위: Point operator++(int)
괄호 안에 의미 없는 int를 넣어 구분해주고, R-value이므로 새 객체를 반환
주로 if (!obj)와 같은 조건문에서 객체의 유효성을 검사하는 용도로 자주 사용
관습적으로 bool 타입을 반환
class Player
{
private:
string Name;
int Level;
public:
// 논리 NOT (!) 연산자 오버로딩
bool operator!() const
{
return Name.empty() ? true : false;
}
};
int main()
{
Player Character;
if (!Character)
{
// ...
}
}
한 객체의 값을 다른 객체에 복사할 때 사용
특히 동적 할당을 사용하는 클래스에서 깊은 복사를 위해 자주 사용
Person& operator=(const Person& other) // 참조자를 반환
{
if (this != &other) // 자기 자신 대입 방지
{
this->name = other.name;
// ...
}
return *this;
}
두 객체의 데이터가 같은지, 혹은 정렬을 위해 크기를 비교할 때 사용
STL 컨테이너(set, map 등)를 쓸 때 < 연산자 오버로딩이 자주 필요
bool operator==(const Point& other) const
{
return (x == other.x && y == other.y);
}
객체를 배열처럼 다룰 수 있게 해줌
리스트나 행렬(Matrix) 클래스를 만들 때 내부 데이터에 접근하는 용도로 사용
두 가지 버젼으로 만들어줘야함
int& operator[](int index) // 참조자를 반환하는 버젼
{
return data[index];
}
int operator[](int index) const // 값만 반환하는 버젼
{
return data[index];
}
a[1] = 10처럼 사용할 수 있으므로 참조자를 반환하는 버젼이 필요하고, void foo(const Person& p)처럼 const객체로 받았을 경우엔 const함수만 사용가능하므로 값만 반환하는 버젼이 필요함지금까지의 연산자 오버로딩과는 조금 결이 다름
보통 p1 + p2를 하면 p1.operator+(p2)가 호출됨. 즉, 왼쪽 객체가 주인공
그런데 출력을 할 때는 std::cout << p1; 처러 사용. 즉, std::cout이 주인공 (cin도 마찬가지)
그러나, std::cout은 표준 라이브러리 클래스이므로 오버로딩하고자 수정할 순 없음
따라서, 클래스 외부에서 따로 함수를 만들어 주는 방식을 사용
ostream, 입력은 istream#include <iostream>
class Point
{
private:
int x, y;
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
// 출력 연산자 선언 (cout)
friend std::ostream& operator<<(std::ostream& os, const Point& p);
// 입력 연산자 선언 (cin)
friend std::istream& operator>>(std::istream& is, Point& p);
};
// 출력 연산자 정의
std::ostream& operator<<(std::ostream& os, const Point& p)
{
os << "X: " << p.x << ", Y: " << p.y;
return os;
}
// 입력 연산자 정의
std::istream& operator>>(std::istream& is, Point& p)
{
is >> p.x >> p.y;
if (is.fail()) // 입력이 잘못되었다면?
{
p = Point(0, 0); // 예외 처리 가능
}
return is;
}
int main()
{
Point p;
std::cin >> p;
std::cout << "p : " << p << std::endl;
}
먼저 friend키워드를 통해 클래스 외부에서 선언된 함수가 Point의 private변수에 접근 가능하도록 선언
함수 반환 타입은 참조자로 하여, 결과값으로 다시 입출력 가능하게 함 (연쇄 법칙)
출력은 const참조자로 매개변수를 받고, 입력은 수정해야하므로 그냥 참조자로 받음
cout / cin은 복사 불가능한 인스턴스라 참조자로 받아야함
주인공은 Point객체가 아니라, cout / cin이라 하였음
하지만 이 객체 안에 수정은 불가능
그러면 얘네 입장에선, <<가 호출되었는데 피연산자 Point를 받는 오버로딩된 함수가 없음
그래서 자동으로 범위를 밖으로 확장하여 맞는 함수를 찾음. ADL(Argument Dependent Lookup)
전역범위에 오버로딩된 함수가 있게 되어 이 함수가 호출되는 것
이는 다른 연산자 오버로딩도 동일하며, 객체 내에 오버로딩하는 경우와 외부에 오버로딩하는 경우가 나뉨
객체의 고유한 행위로, 전역 함수로 만들 수 없게 막아놓은 연산자들 (강제)
= (대입 연산자)
[] (배열 인덱스 연산자)
() (함수 호출 연산자)
-> (멤버 접근 포인터 연산자)
객체의 상태를 직접 바꾸는 연산자들도 객체 내부에 하면 좋다 (추천)
복합 대입 연산자 (+=, -=, *=)
증감 연산자 (++, --)
교환법칙같은 대칭성이 필요할 때 (Point + 3, 3 + Point)
근데, 교환법칙하려 했더니 왼쪽 피연산자가 내가 만든 클래스가 아닐 때 (cout, int, double 등)
이럴 때 전역범위에 해주면 유연성이 생기게 된다
// 전역범위
Point operator*(double d, const Point& p)
{
return Point(p.x * d, p.y * d);
}
p2 = 3 * p1;
산술 연산자 (+, -, *, /)
비교 연산자 (==, !=, <, >)
입출력 연산자 (<<, >>)
전역에 선언했기에 friend를 사용하면서 캡슐화가 약해지고, 결합도가 높아짐
클래스 내부에도 friend 함수 선언이 많아져 클래스 내용보다 친구 목록이 더 길어지는 상황 발생 가능
전역 범위에 함수를 너무 많이 만들면 관리가 힘들어짐