C++/C++ : Study

8. 연산자 오버로딩 (1) - 소개

더블유제이플로어 2025. 6. 8. 00:01

◆  연산자 오버로딩

이번 연산자 오버로딩 강의가 재미있는 이유
1. 지금 까지 배운 내용을 종합적으로 이해를 해야 코드를 이해할 수 있다.
퍼즐 푸는 것처럼 능력을 테스트할 수 있는 기회이다.
2. 굉장히 C++ 의 특화된 기능이다
Java는 연산자 오버로딩이 없다.
C++을 배우고 연산자 오버로딩을 할 줄 알면 좋다.
어려울 수도 , 재미있을 수도 있으나 재미있으면 좋겠다.
이 강의가 재밌으려면 지난 강의들이 다 이해가 되어야 할 수 있다.


Operator Overlading

◆  연산자 오버로딩

◆  멤버 함수인 연산자 오버로딩
◆  전역 함수인 연산자 오버로딩
◆  스트림 삽입 및 추출 연산자 오버로딩
◆  대입 연산자 오버로딩
◆  첨자 연상자 오버로딩

강의 순서는 개요부터 시작한다.
연산자 오버로딩을 하는 2가지 방법
멤버 함수 / 전역 함수 연산자 오버로딩을 배운다.

그 이후에 스트림 삽입 및 추출 연산자 / 대입 연산자 / 첨자 연산자
오버로딩에 대해서 배울 예정이다.


중요한 Part이다.

◆  연산자 오버로딩

   ●   +, = , * 등 연산자를 유저 정의 타입(ex. 클래스)에 사용할 수 있도록 하는 기능
        ☞    player1 + player2
   ●  유저 정의 타입을 기본 타입(int, float, etc)과 유사하게 동작하게 할 수 있다
   ●  코드를 보다 이해하기 쉽고, 작성하기 쉽게 한다.
        ☞    항상 좋은 것은 아니다. 직관성과 가독상을 높인다고 판단될 경우 활용.
   ●  대입 연산자(=)를 제외하면 자동 생성되지 않으며, 사용자가 구현해주어야 한다.

◆  연산자 오버로딩 기능 자체도 의미 있지만, 지금까지 배운 모든 내용이 종합적으로 적용되므로 이해하지 못한 내용이 없는지 점검 필요.

연산자 오버로딩이란 무엇일까?
연산자는 + - * / -> C++ 첫 시작에 이야기하였다
연산자를 오버로딩하는것이다. 오버라이딩과 헷갈리지 말아야 한다.


*AI Claude 오버로딩과 오버라이딩 간단 비교

오버로딩(Overloading) = " 같은 이름, 다른 역할"

// 함수 오버로딩
void print(int x);
void print(string s);
void print(double d);

// 연산자 오버로딩
Vector operator+(const Vector& other); // + 연산자에 새로운 의미 부여

오버라이딩(Overriding) = " 부모 것을 새롭게 재 정의"

class Entity{
	virtual void Move() { cout << "Entity moves"; }
};

class Player : public Entity {
	void Move() override { cout << "Player runs fast"; } // 부모 함수 재정의
};

핵심 차이점
오버로딩
● 같은 이름의 함수 / 연산자를 여러 개 만드는 것
● 매개변수가 달라야 함

오버라이딩
● 부모 클래스의 가상 함수를 자식 클래스에서 다시 구현하는 것
● 상속 관계에서만 발생

쉬운 기억법
● 오버로딩 = "로딩"이 많다 = 같은 이름 여러 개
● 오버라이딩 = "라이딩"해서 덮어쓴다 = 부모 것을 새로 써서 덮음


이름이 똑같은 함수를 여러 개 만들 수 있다 - 오버로딩
이번 시간에 연산자를 오버로딩 하는 방법을 배운다.

연산자를 왜 오버로딩 해야 할까?

개발자가 클래스를 만들었다는 이야기는 (ex. Player Class)
Player를 갖는 변수-> 객체 (ex. Player p;)를 만들 수 있었다.
Player p1, Player p2를 선언하였고

Player p3 = p1 + p2; 를 하면 불가능하다.

int  a =1; int b =2;
int c = a+b; 이렇게 선언 가능하다
선언할 수 있는 이유는
a에 저장되어 있는 1 과 b 에 저장되어있는 2를 더해
변수 c에 대입할 수 있다.
a와 b의 타입은 int이다.

Player 와 int 색상 차이가 난다.

int , float , boolean , Character 등은 기본 데이터 타입이라고 이야기하였다.
기본 데이터 타입은 개발자가 아무런 작성하지 않아도
기본적으로 C++에 정의되어 있는 데이터 타입이다.
int는 4byte를 차지하고 있는 등 미리 정의되어 있다.

그런데 Player 데이터타입은 어떤가? 개발자가 만들었다.
어떤 공간을 할당해야 하는지 컴파일러가 미리 알고 있는 게 아니고
개발자가 직접 코드를 작성해서 구현해야 한다.
그런 클래스는 사용자 정의 또는 유저 정의 타입이라고 한다.

유저 정의 타입은 + , - , = , * 등등 연산자를 기본적으로 사용이 불가능하다.
player1 + player2 가 기본적으로는 불가능하다.
왜 불가능하냐면 컴파일러가 어떻게 해야 하는지 모르기 때문이다.
+ 연산을 진행할 때도 어떻게 해야 하는지 개발자가 구현해야 한다.
정의할 수 있는 방법이 연산자 오버로딩이다.

기본 타입(int,  float 등등) 은 기본적으로 연산자를 사용할 수 있다.
클래스 객체에 대해서는 불가능하다.
그러나 연산자 오버로딩을 하면 유사하게 사용할 수 있다.
 
연산자 오버로딩 하는 이유는 코드를 이해하기 쉽고 작성하기 쉽게 한다.
연산자 오버로딩을 하지 않아도 똑같은 기능을 하는 코드를 만드는 데 전혀 문제가 없다.
연산자 오버로딩은 보기 쉽고 읽기 쉬운 사용하기 쉬운 코드를 만들기 위해 사용된다.
연산자 오버로딩을 한다면 무조건 좋은가? 세상엔 무조건이란 없다.
연산자 오버로딩이 필요할 때 사용하면 좋다.


◆  연산자 오버로딩

   ●  숫자 하나를 갖는 Number 클래스를 예시로 작성하고,
   클래스 객체끼리 사칙 연산 기능이 필요한 경우 (a+b) * (c/d)를 계산해야 한다면,

   ●  전역 함수를 사용하여 구현
   Number result = multiply(add(a, b) , divide(c, d));
   ●  멤버 함수를 사용해 구현
   Number result = (a.add(b)). multiply(c.divide(d));
   (이렇게 구현하면 너무나 실수 가기 쉽다.)

   ●  연산자 오버로딩을 사용하여 구현
   Number result = (a+b)*(c/d);


다른 간단한 예제를 보자

#include <iostream>

class Number
{
private:
    int val;
public:
    Number(int val)
        :val{ val } { }
};

int main()
{
    Number n1{ 5 };
    Number n2{ 10 };

    Number n3 = n1 + n2; // ERROR!
}

n3 에 15를 넣고 싶으면 연산자 오버로딩을 해야한다.

class Number
{
private:
    int val;
public:
    Number(int val)
        :val{ val } { }
    Number Add(Number b)
    {
        return Number{ val + b.val };
    }
    void Print()
    {
        cout << val << endl;
    }
};

Number 클래스에 멤버 함수로 Add 를 추가하여
또 다른 Number 를 인자로 받도록 한다.
반환형이 Number 이고, 인자가 Number 인 멤버 함수를 구현하였다.
함수 내용은 새로운 Number 객체를 만들어서 반환을 하는데
Number 객체는 인자로 가진 Number 객체와 멤버 변수를 더하여 반환한다.

int main()
{
    Number n1{ 5 };
    Number n2{ 10 };

    Number n3 = n1.Add(n2);
    n3.Print(); // 15
}

또 다른 방법은 전역 함수를 만드는 것이다.

Number Add(Number n1, Number n2)
{
    return Number{ n1.val + n2.val };
}

또 다른 방법은 멤버 함수가 아닌 전역 함수를 만들어서
반환형에 Number, Number 인자를 2개 받고
함수 내용은 n1 의 val 와 n2 의 val 를 더하여
새로운 Number 객체를 생성해서 반환할 수 있다.

기존 Number 클래스를 사용하면 val 부분에서 에러가 나올 수 있다.

class Number
{
private:
    int val;
public:
    Number(int val)
        :val{ val } { }
    Number Add(Number b)
    {
        return Number{ val + b.val };
    }
    void Print()
    {
        cout << val << endl;
    }
    int GetValue()
    {
        return val;
    }
};

Number Add(Number n1, Number n2)
{
    return Number{ n1.GetValue() + n2.GetValue() };
}

본문으로 넘어가면 + 이외에 복잡한 식을 필요로 한다고 가정해보자.
전역 함수 또는 멤버 함수를 이용해서 만들면
(a+b) *(c/d) 를 사용하고 싶다면
multiply 함수, add 함수, divide 함수를 만들어서 사용해야한다.
해석하는데 굉장히 피곤하다.
연산자 오버로딩을 하게되면 간단하게 (a+b) *(c/d) 를 사용할 수 있다.

◆  연산자 오버로딩 예제

class Point {
private:
    int xPos;
    int yPos;
public:
    Point(int x = 0, int y = 0)
        :xPos{ x }, yPos{ y }
    {
    }
    void ShowPosition() const
    {
        cout << "[" << xPos << ", " << yPos << "]" << endl;
    }
};

int main()
{
    Number n1{ 5 };
    Number n2{ 10 };

    Number n3 = n1.Add(n2);
    n3.Print(); // 15

    Number n4 = Add(n1, n2);
    n4.Print(); // 15
}

 

이번 강의에서는 Point 클래스를 이용하여 연산자 오버로딩을 알아보자.
좌표값 2개를 받는  int xPos, int yPos 멤버 변수가 있다.
ShowPosition() 멤버 함수는 x 와 y 의 값을 보여주는 함수이다.


◆  연산자 오버로딩 기본 규칙 

   ●  연산의 우선순위를 바꿀 수 없다. ( * 는 + 보다 먼저 계산)
   ●  단항, 이항, 삼항 연산의 교체 불가능
   ●  기본 타입(int, float, etc)에 대한 연산자는 오버로딩 불가능
   ●  새로운 연산자의 정의 불가

◆  연산자는 멤버 함수 또는 전역 함수로 오버로딩 가능

   ●  [ ] , ( ) , -> , = 등 몇몇 연산자는 멤버 함수로만 오버로딩 가능.

연산자 오버로딩에서 제약 사항이 있다.
1. 연산의 우선 순위를 바꿀 수는 없다.
* 는 + 보다 먼저 계산된다.
p1 * p2 + p3 을 하게 되면
괄호가 없는 경우에는 수학 공식과 똑같이
(p1 * p2) + p3 가 계산이 된다.

2. 단한, 이항, 삼항 연산자에 대해서 추후에 설명할 예정인데
그것을 바꾸는 것은 불가능 하다.

3. 연산자 오버로딩은 개발자가 만든 클래스에 대해서만 계산이 가능하다
int 변수 등 기본 타입 연산자를 개발자 마음대로 바꿀 수는 없다.
기본 타입의 오버로딩은 불가능 하다.

4. 없는 기본 연산자 타입을 새로 만들어내는것도 불가능 하다.
이미 존재하는 기본 연산자 타입을 클래스에 대해서 동작할 수 있게 만드는 것이다.

이 강의에서 중요한게 연산자를 오버로딩하는 방법이다.

멤버 함수 또는 전역 함수로 오버로딩 하는 방법이다.
몇몇 연산자들 [ ] , ( ) , -> , = 등은 예외적으로 멤버 함수로만 오버로딩이 가능하다.


◆  연산자 오버로딩 사용 목적

   ●  Point는 기본 자료형이 아닌 사용자 정의 자료형(클래스)이다.
   ●  연산자 오버로딩을 통해 연산자에 의한 객체의 동작 정의 가능하다.

int main()
{

    Point p1{ 10,20 };
    Point p2{ 30,40 };

    Point p3 = p1 + p2; // 가능하도록 연산자 오버로딩을 할 예정이다.
		// p3.xPos = 40, p3.yPos = 60 이 될 예정이다.

    cout << (p1 < p2) << endl;
    cout << (p1 == p2) << endl;

    cout << p3 << endl;
}
현재는 불가능하지만, 이런 식으로 사용하고 싶은 경우
=> 연산자 오버로딩을 하면 가능하다.

Operator Overlading

◆  연산자 오버로딩 : 클래스에 대한 연산자의 적용 방식을 사용자가 직접 오버로딩하여 구현할 수 있다.

◆  멤버 한수인 연산자 오버로딩
◆  전역 함수인 연산자 오버로딩
◆  스트림 삽입 및 추출 연산자 오버로딩
◆  대입 연산자 오버로딩
◆  첨자 연상자 오버로딩

 

https://youtu.be/S6MkdRzJjjg?si=wu89FqCc9WgIjdNM

조금 시간이 오래걸린 강의였는데, 확실히 한 흐름으로 읽으면 재밌는 강의이다.

제대로 연산자 오버로딩을 하는걸 사용하고 싶다.