[STL] 연산자 오버로딩(1)

치치·2025년 1월 10일

STL

목록 보기
1/21
post-thumbnail

📌 연산자 오버로딩

기존의 연산자를 우리가 정의한 클래스나 구조체에 맞게 동작하도록 재정의 하는 것을 말한다
-> 멤버함수로 정의할 것이다 (클래스 내부)

예를 들어 바로 코드를 보겠다

이 코드에서는 내가 원하는 건 box1객체와 box2객체의 box, apples변수의 값을 각각 더한 걸 출력하고 싶었다
하지만 box3객체에서 에러가 발생!

#include <iostream>
using namespace std;

class AppleBox
{
private:
	int box; // 사과박스 수
	int apples; // 사과의 수

public:
	AppleBox(int Box, int Apples)
	{
		box = Box;
		apples = Apples;
	}
};

int main()
{
	AppleBox box1(10,5);
	AppleBox box2(5,10);

	AppleBox box3 = box1 + box2;

}

에러가 발생한 이유

  • 기존의 연산자 (+, -, %, / )들은 c++이 아는 데이터 타입인 정수나 실수와 같은 타입에만 적용된다
  • 하지만 위의 코드에서 AppleBox 클래스사용자 정의 타입이라 메인함수에서 저렇게 선언을 해도 뭐랑 뭐가 연산하는 지 모르는 것!!
  • 우리가 클래스 내부에 연산을 정의해주어야 한다

연산자 오버로딩 사용방법

  • 여기서 box1 + box2를 구현하려면 클래스 내부에 연산자 오버로딩을 정의해주어야한다
    -> 현재 << 연산자를 배우지 않았기 때문에 클래스 내부에 출력함수를 따로 만들어두었다

  • 출력함수에 const를 사용한 이유
    : 이 함수는 객체의 멤버 변수를 변경하지 않겠다는 뜻
    즉, box와 apples변수는 값이 변경되지 않고 반환하겠다는 뜻임

객체 생성 후 연산

  • 여기서 box3 = box1 + box2가 의미하는 것
    : box1 객체 기준으로 멤버 함수 operator( )를 호출하고, 인자로 box2를 전달
    클래스 내에 operator( ) 함수가 정의되어 있지 않으면 에러!

    위의 틀린 예시에서도 오버로딩 함수가 정의되어 있지 않았기 때문에 인자로 전달할 장소가 없었다 -> 그러니까 에러

AppleBox 전체코드

#include <iostream>
using namespace std;

class AppleBox
{
private:
	int box; // 사과박스 수
	int apples; // 사과의 수

public:
	AppleBox(int Box, int Apples)
	{
		box = Box;
		apples = Apples;
	}

	AppleBox operator + (const AppleBox& other)
	{
		return AppleBox(box + other.box, apples + other.apples);
	}

	void print() const 
	{
		cout << "박스 수: " << box << ", 사과 수: " << apples << endl;
	}
};



int main()
{
	AppleBox box1(10,5);
	AppleBox box2(5,10);

	AppleBox box3 = box1 + box2;
	

	box3.print();
}

출력값:


[STL 책 예시]

  • 연산자 오버로딩으로 받은 x와 y값을 연산하고 return
  • 위에서 했던 코드와 전체적으로 다른 건 없다 (방식은 다 같음, 이해를 위해 한번 더 코드보기)
#include <iostream>
using namespace std;

class Point
{
private:
	int x;
	int y;

public:
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }

	void Print() const
	{
		cout << x << ',' << y << endl;
	}
	
	const Point operator + (const Point& arg)
	{
		Point pt;
		pt.x = this->x + arg.x;
		pt.y = this->y + arg.y;

		return pt;
	}
};

int main()
{
	Point p1(2, 3), p2(5, 5);
	Point p3;

	p3 = p1 + p2;
	p3.Print();
	p3 = p1.operator+(p2);
	p3.Print();

	return 0;
}
  • 메인함수 부분에서 p3 = p1 + p2 즉, p3 = p1.operator + (p2)와 같은 의미이다
  • P3 = p1 + p2는 컴파일러가 우리가 정의해준 걸 토대로 해석해서 호출해준다
  • p3 = p1.operator + (p2)는 직접 함수를 호출해주는 방식
  • 결론은 똑같은 값이 출력이 된다

    출력값:


📌 단항연산자 오버로딩

단항연산자란?

  • & ~ + - ++ -- 와 같이 피연산자를 하나만 사용하는 연산자를 말한다
  • 부호연산자(+, -), 증감연산자(++, --), 논리 부정 연산자(!) 등이 포함
  • ++ 연산자와 -- 연산자를 전위와 후위를 통해 알아보겠다

[STL 책 예시] - 전위/후위 증가

  • 클래스 내부에 연산에 사용될 x, y 변수를 선언해주고 생성자 초기화 리스트를 이용하여 초기값 0으로 지정

  • 전위증가는 operator++ ( ) 함수
    -> 전위증가는 값을 증가하고 바로 현재 객체(해당 클래스를 참조하는 객체)를 참조하는 형태로 반환한다 -> return * this

  • 후위증가는 operator++ (int) 함수
    -> 증가하기 전의 값을 반환할 객체 생성 후 이 객체를 반환하고 클래스 내부의 변수의 값은 증가시켜둔 상태

#include <iostream>
using namespace std;

class Point
{
private:
	int x;
	int y;

public:
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}

	void Print() const
	{
		cout << x << ',' << y << endl;
	}

	const Point& operator++ ()
	{
		++x;
		++y;
		return *this;
	}

	const Point& operator++(int)
	{
		Point pt(x, y);
		++x;
		++y;
		return pt;
	}
};


int main()
{
	Point point1(2, 3);
	Point point2(2, 3);

	Point result;

	result = ++point1; // 전위 증가
	point1.Print();
	result.Print();

	result = point2++; // 후위 증가
	point2.Print();
	result.Print();

	return 0;
}

전위증가와 후위증가를 어떻게 구별할까?

  • 후위 증가 함수에는 매개변수로 int가 설정이 되어있다
    -> 진짜 매개변수를 받는다는 의미는 아니고, 전위와 후위 함수를 구별하기 위해 사용된다
    -> 메인함수에서 후위 증가를 호출하면 컴파일러가 자동으로 더미값(0)을 인수로 전달하여 후위함수쪽으로 이동하여 수행

메인함수 결과값

  • 전위 증가의 경우, 새 객체(result)에 담은 결과도 증가되었고, 기존 객체(point1)의 결과값도 증가되었다
  • 후위 증가의 경우, 새 객체(result)에 담은 결과는 이전값이고, 기존 객체(point2)의 값은 증가되었다


[STL 책 예시] - 전위/후위 감소

  • 전위/후위 증가와 코드가 동일하다
#include <iostream>
using namespace std;

class Point
{
private:
	int x;
	int y;

public:
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}

	void Print() const
	{
		cout << x << ',' << y << endl;
	}

	const Point& operator-- ()
	{
		--x;
		--y;
		return *this;
	}

	const Point& operator--(int)
	{
		Point pt(x, y);
		--x;
		--y;
		return pt;
	}
};


int main()
{
	Point point1(2, 3);
	Point point2(2, 3);

	Point result;

	result = --point1; // 전위 증가
	point1.Print();
	result.Print();

	result = point2--; // 후위 증가
	point2.Print();
	result.Print();

	return 0;
}

결과값:


📌 이항연산자 오버로딩

이항연산자란?

피연산자가 2개인 연산자
산술연산자(+, -, *, /), 문자열 결합 연산자(+), 비교연산자(<, >), 논리연산자(&&, ||), 대입연산자(==, -=, +=) 등이 포함된다

==연산자 오버로딩

  • == 연산자는 비교 연산으로 연산 결과에 따라 true 또는 false를 반환하는 bool 타입이다
#include <iostream>
using namespace std;

class Point
{
private:
	int x;
	int y;

public:
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
	
	void Print()
	{
		cout << x << ',' << y << endl;
	}

	bool operator == (const Point& arg) const
	{
		return x == arg.x && y == arg.y ? true : false;
	}
};


int main()
{
	Point p1(2, 3), p2(5, 5), p3(2, 3);

	if (p1 == p2)
	{
		cout << "p1 == p2입니다" << endl;
	}

	if (p1 == p3)
	{
		cout << "p1 == p3입니다" << endl;
	}

	return 0;
}

삼항연산자

  • ==연산자 오버로딩 부분을 이렇게 삼항연산자로도 짤 수 있다
  • 삼항연산자란?
    피 안션자가 3개인 연산자
  • 코드의 의미는 x가 인수로 들어온 객체의x값과 같고 y도 인수로 들어온 객체의 y값과 같은 경우 true를 반환 (AND연산자로 둘 다 true여야 true를 반환)
  • 삼항연산자를 사용하지 않고 풀어서 작성해도 제대로 작동한다
    그래도 삼항연산자를 사용하면 좀 더 코드를 간결하게 작성할 수 있을 거 같다

메인함수 결과값

  • 메인함수에서 if(p1 == p2)의 의미는 p1.operator == (p2)와 같다
  • if(p1 == p3)의 의미는 p1.operator == (p3)와 같다


!=연산자 오버로딩

#include <iostream>
using namespace std;

class Point
{
private:
	int x;
	int y;

public:
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
	
	void Print()
	{
		cout << x << ',' << y << endl;
	}

	bool operator == (const Point& arg) const
	{
		return x == arg.x && y == arg.y ? true : false;
	}

	bool operator != (const Point& arg) const
	{
		return !(*this == arg);
	}
};


int main()
{
	Point p1(2, 3), p2(5, 5), p3(2, 3);

	if (p1 != p2)
	{
		cout << "p1 != p2입니다" << endl;
	}

	if (p1 != p3)
	{
		cout << "p1 != p3입니다" << endl;
	}

	return 0;
}
  • return !(*this == arg) 의 의미
    현재 객체의 값과 매개변수로 들어온 값이 같다의 부정!!!!
  • 두 값이 서로 다르다면 true를 반환
  • 두 값이 서로 같다면 false를 반환

메인함수 결과값

  • p1(2, 3) p2(5, 5) 로 서로의 값이 다르다 !
  • p1 != p2 가 서로 다르기 때문에 !=연산자가 true


전역함수로 연산자 오버로딩

위에서 작성한 연산자 오버로딩들의 예시는 모두 멤버함수로 구현하였다
하지만 멤버함수로 작성하였을 때 동작이 안되는 경우도 있는데 이럴때는 전역함수로 연산자 오버로딩을 구현해주어야 한다! (클래스 외부)

멤버함수로 안되는 경우 : 왼쪽 피연산자가 연산자 오버로딩 객체가 아닐경우

(왼쪽 피연산자가 해당 클래스의 객체가 아닐 경우를 의미)
ex) k + p1 을 연산하는 경우

class Point
{
 ---
}
int main()
{
	Point p1(2,3);
    Point p2(1,3);
    int k;
    
    p1 + p2;
    k + p1;
    
    return 0;
}
  • p1 + p2의 경우 각각은 해당 클래스의 객체이다
    -> 연산자 오버로딩을 호출하면 p1.operator + (p2)를 의미한다
  • 하지만, k의 경우 해당 클래스의 객체가 아니기 때문에 이런식으로 호출할 수 없으며 operator + (k, p1) 처럼 호출해야한다

전역함수를 사용한 이항연산자 오버로딩

ex) p3 = p1 - p2를 연산하는 경우

#include <iostream>
using namespace std;

class Point
{
private:
	int x;
	int y;

public:
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}

	void Print()
	{
		cout << x << ',' << y << endl;
	}
	int GetX() const
	{
		return x;
	}
	int GetY() const
	{
		return y;
	}
};

// 전역함수
const Point operator - (const Point& argL, const Point& argR)
{
	return Point(argL.GetX() - argR.GetX(), argL.GetY() - argR.GetY());
}

int main()
{
	Point p1(2, 3), p2(5, 5);
	Point p3;

	p3 = p1 - p2;
	p3.Print();

	return 0;
}
  1. 연산자 오버로딩을 전역함수로 선언한다

  2. 인자로 메인함수에서 p1과 p2를 받아온다

  3. 해당 클래스의 GetterX, GetterY함수로 접근하여 private로 선언해둔 x, y 변수에 접근한다
    -> GetX( ), GetY( ) 함수 말고 friend함수를 사용하는 것도 가능하다

  4. 서로의 클래스 객체의 값을 연산하여 반환


friend 함수

특정 상황에서 클래스 내에 접근하지 못하도록 private로 접근 제한을 두었는데, 필요의 경우 해당 클래스나 함수에서 접근 가능하도록 사용하는 함수
프렌드 함수는 캡슐화를 저해하므로 가능하면 위에서 사용한 Get,Set함수를 사용한다!

  • 함수나 클래스를 프렌드로 지정하면 모든 클래스 멤버를 접근 제한 없이 사용가능하다!

ex) Point 클래스 내부에 전역함수를 friend로 지정
-> 인자로 들어온 argL와 argR는 해당 클래스의 x변수와 y변수에 바로 접근이 가능해진다

  • 캡슐화 저해
    friend함수를 사용하면 해당 클래스의 private로 정의된 변수나 함수를 사용하기 때문에, 접근이 너무 쉬워지고 값의 변동이 생길 수 있기 때문이다


📌 함수 호출 연산자 오버로딩 ( ( )연산자 )

함수 호출 연산자란?

객체를 함수처럼 호출할 수 있게 하는 기능이다
클래스에 operator( )를 정의하면 그 클래스의 객체를 함수처럼 호출이 가능하다
생김새는 함수가 아니지만 기능이 함수와 같은 역할 느낌

ex) Adder 클래스를 생성하고 생성자 초기화 리스트로 인자로 받아온 값을 value로 지정
메인함수에 작성한 Adder adder객체를 함수 객체라고 한다

#include <iostream>
using namespace std;

class Adder {
private:
    int value;
public:
    Adder(int init) : value(init) {}

    int operator()(int x) {
        value += x;
        return value;
    }
};

int main() {
    Adder adder(10);  // 초기 값 10
    cout << adder(5) << endl;  // 15 출력
    cout << adder(10) << endl; // 25 출력
    return 0;
}
  • 함수는 아니지만 클래스 내부에 operator( ) 선언
    -> 메인함수에서 클래스의 객체를 생성한 뒤 함수에 접근하지 않고 바로 객체를 함수처럼 사용 가능하다!
    -> 위의 코드를 보면 메인함수에서 객체에 바로 매개변수를 넣어 함수처럼 사용중!
    -> 직관적으로 볼 수 있어서 편하다

  • operator( )가 아닌 기존 함수처럼 사용했더라면
    -> 클래스 내부에 int add함수를 정의해두고 메인함수에서 객체를 생성한 뒤 객체로 접근해서 함수를 호출해야한다

임시객체

임시객체로도 사용이 가능하다

임시객체란?
잠깐 생성되었다가 한번 쓰이고 바로 소멸되는 객체를 말한다
쉽게 말하자면, 객체의 이름을 정하지 않고 바로 생성해서 사용하는 객체를 뜻한다 (잠깐 쓰고 버리는 용도 - 자동소멸)

위의 코드처럼
클래스의 객체 이름을 생성해서 사용해도 된다
-> 이렇게 사용할 경우 객체가 명확히 존재하기 때문에 재사용이 가능하다

  • 임시 객체를 사용할 경우 (객체 이름없음)
profile
뉴비 개발자

0개의 댓글