4 minute read

1. 연산자 오버로딩의 이해와 유형

✏️Operator+

#include <iostream>
using namespace std;

class Point 
{
private:
	int xpos, ypos;
public:
	Point(int x=0, int y=0) : xpos(x), ypos(y)
	{  }
	void ShowPosition() const
	{
		cout<<'['<<xpos<<", "<<ypos<<']'<<endl; 
	}
	Point operator+(const Point &ref)    //operator+라는 이름의 함수
	{
		Point pos(xpos+ref.xpos, ypos+ref.ypos);
		return pos;
	}
};

int main(void)
{
	Point pos1(3, 4);
	Point pos2(10, 20);
	Point pos3=pos1.operator+(pos2);

	pos1.ShowPosition();
	pos2.ShowPosition();
	pos3.ShowPosition();
	return 0;
}
  • 객체끼리 더할 수 있다.

컴파일러는 pos3 = pos1 + pos2pos3=pos1.operator+(pos2) 로 해석하여 실행한다.

  • C++의 객체지향이 나타나는 부분 중 하나라고 생각된다. 기본 자료형 변수처럼 객체도 완벽히 기본 자료형 데이터처럼 취급할 수 있기 때문에 덧셈, 뺄셈, 나눗셈, 곱셈 등의 연산들을 가능하게 하려 한다.

✏️연산자 오버로딩

  • 멤버함수에 의한 연산자 오버로딩 pos1.operator+(pos2)
  • 전역함수에 의한 연산자 오버로딩 operator+(pos1, pos2)
#include <iostream>
using namespace std;

class Point
{
private:
	int xpos, ypos;
public:
	Point(int x=0, int y=0) : xpos(x), ypos(y)
	{  }
	void ShowPosition() const
	{
		cout<<'['<<xpos<<", "<<ypos<<']'<<endl; 
	}
	friend Point operator+(const Point &pos1, const Point &pos2);
    // friend선언을 해주면 operator+ 함수는 Point클래스의 private에 접근할 수 있게 된다.
};

Point operator+(const Point &pos1, const Point &pos2)
{
	Point pos(pos1.xpos+pos2.xpos, pos1.ypos+pos2.ypos);
	return pos;
} 

int main(void)
{
	Point pos1(3, 4);
	Point pos2(10, 20);	
	Point pos3=pos1+pos2;

	pos1.ShowPosition();
	pos2.ShowPosition();
	pos3.ShowPosition();
	return 0;
}

  • friend 선언을 함으로써 이 클래스는 + 연산에 대해 오버로딩이 되어있다는 정보를 쉽게 확인할 수 있다.
  • 객체지향은 전역의 개념이 없지만 c++은 C의 구현이 가능하기 때문에 전역의 개념이 남아있다. 그러나 멤버함수를 기반으로 연산자 오버로딩을 하는 것이 좋다.

✏️오버로딩이 가능한 연산자

  • 멤버함수 기반으로만 오버로딩이 가능한 연산자
    • = 대입연산자
    • ( ) 함수 호출 연산자
    • [] 배열 접근 연산자
    • -> 멤버 접근을 위한 포인터 연산자

✏️주의사항

  • 본래 의도를 벗어난 형태로 오버로딩하는 것은 좋지 않다
    • pos1+pos2를 보고 우리가 기대하는 것은 pos1과 pos2를 더해 그 결과로 객체를 만들어 반환하는 것이다.
    • 그러나 생뚱맞게 pos1을 pos2만큼 증가시킨다거나, 연산 결과가 pos2에 저장된다면 곤란하다
  • 매개변수 디폴트 값 설정이 불가능하다

  • 연산자 순수 기능까지 뺏을 순 없다
    • operator+ 인데 하는 연산은 곱셈이라던지, 여기서 이러면 안된다.

😀 함수가 오버로딩 되고, 컴파일러는 인자로 호출을 구분한다. 이와 유사하게, 연산자가 오버로딩 되면, 피연산자의 종류에 따라 연산의 방법이 바뀐다.

2. 단항 연산자 오버로딩

  • ++pos = pos.operator++()(멤버함수), operator++(pos)(전역함수)
#include <iostream>
using namespace std;

class Point 
{
private:
	int xpos, ypos;
public:
	Point(int x=0, int y=0) : xpos(x), ypos(y)
	{  }
	void ShowPosition() const
	{
		cout<<'['<<xpos<<", "<<ypos<<']'<<endl; 
	}
	Point& operator++()     // this는 객체자신의 포인터 값을 의미하므로, *this는 객체 자신을 의미한다
	{                       // 반환형이 참조형으로 선언되었으므로 객체 자신을 참조할 수 있는 참조값을 반환한다
		xpos+=1;            
		ypos+=1;
		return *this;
	}
	friend Point& operator--(Point &ref);
};

Point& operator--(Point &ref)
{
	ref.xpos-=1;
	ref.ypos-=1;
	return ref;
}

int main(void)
{
	Point pos(1, 2);
	++pos;
	pos.ShowPosition();
	--pos;
	pos.ShowPosition(); 

	++(++pos);              //먼저 ++(pos.operator++())로 해석된다. 이어서 (참조값).operator++()로 해석된다
	pos.ShowPosition(); 
	--(--pos);              //--(operator--(pos))로 해석된 후 이어서 operator--(참조값)으로 해석된다
	pos.ShowPosition();
	return 0;
}
  • 참조값을 반환함으로써 ++(++pos)와 같은 연속적인 연산이 가능해진다

✏️전위증가 후위증가 구분

++pos = pos.operator++() pos++ = pos.operator++(int) //후위

--pos = pos.operator--() pos-- = pos.operator--(int) //후위

  • int형 인자를 전달한다는 뜻이 아니라 구분을 위해 써줌
#include <iostream>
using namespace std;

class Point 
{
private:
	int xpos, ypos;
public:
	Point(int x=0, int y=0) : xpos(x), ypos(y)
	{  }
	void ShowPosition() const
	{
		cout<<'['<<xpos<<", "<<ypos<<']'<<endl; 
	}
	Point& operator++()         //전위 증가
	{
		xpos+=1;
		ypos+=1;
		return *this;
	}
	const Point operator++(int)     //후위 증가
	{                               
		const Point retobj(xpos, ypos);   // const Point retobj(*this);
		xpos+=1;
		ypos+=1;
		return retobj;
	}
	friend Point& operator--(Point &ref);
	friend const Point operator--(Point &ref, int);
};

//전역함수
Point& operator--(Point &ref)           //전위 감소
{
	ref.xpos-=1;
	ref.ypos-=1;
	return ref;
}

const Point operator--(Point &ref, int)     //후위 감소
{                                           
	const Point retobj(ref);
	ref.xpos-=1;
	ref.ypos-=1;
	return retobj;
}

int main(void)
{
	Point pos(3, 5);
	Point cpy;
	cpy=pos--;
	cpy.ShowPosition();
	pos.ShowPosition();

	cpy=pos++;
	cpy.ShowPosition(); 
	pos.ShowPosition();
	return 0;
}
  • 값을 증가 시키기 전에 객체를 복사해놓고 값을 증가시켜준다. 그러면 값은 증가되지만 복사본을 반환하면서 후위 증가의 효과를 낼 수 있다. 이때 복사본은 변경되면 안되므로 const선언을 해준다.

✏️반환형에서의 const선언과 const객체

const Point operator--(Point &ref, int)     
{                                           
	const Point retobj(ref);        //함수 내에서 retobj의 변경을 막겠다
	ref.xpos-=1;
	ref.ypos-=1;
	return retobj;
}
  • 반환형이 const로 선언된 의미

    operator– 함수의 반환으로 인해서 생성되는 임시객체를 const 객체로 생성하겠다!

  • const Point pos(3,4) 이것이 const 객체이다. 상수로 취급하고 객체에 저장된 값을 변경하지 못하게 한다. 따라서 const로 선언되지 않은 함수를 호출 할 수 없다. 그리고 이러한 객체를 참조할 때도, 참조자에 const선언을 해주어야 한다.
    즉, const로 선언된 후위 증가 함수는 반환되는 참조값(임시객체)도 변경될 수 없기때문에, (pos–)–이런 연산은 컴파일 에러가 난다.

3. 교환법칙

  • A + B = B + A

  • 마찬가지로 cpy = 3 * poscpy = pos * 3 둘다 연산이 가능하게 해주려면 전역함수를 기반으로 연산자 오버로딩을 해준다

#include <iostream>
using namespace std;

class Point 
{
private:
	int xpos, ypos;
public:
	Point(int x=0, int y=0) : xpos(x), ypos(y)
	{  }
	void ShowPosition() const
	{
		cout<<'['<<xpos<<", "<<ypos<<']'<<endl; 
	}
	Point operator*(int times)      
	{
		Point pos(xpos*times, ypos*times);
		return pos;
	}
    friend Point operator*(int times, Point &ref);
};

Point operator*(int times, Point &ref){
    return ref*times;
}

int main(void)
{
	Point pos(1, 2);
	Point cpy;

	cpy=pos*3;      //cpy = pos.operator*(3);
	cpy.ShowPosition();

	cpy=3*pos*2;    // cpy = (operator*(3, pos)).operator*(2); 
	cpy.ShowPosition();
	return 0;
}

Tags: ,

Categories:

Updated: