본문 바로가기

C언어 기초

[뇌를 자극하는 STL] 1장 . 연산자 오버로딩

연산자 오버로딩

: c++에서 제공하는 기본 타입이 아닌

: 클래스, 사용자 정의 타입에도 연산자를 사용할 수 있게 하는 문법

 

>>연산자 오버로딩의 핵심

: 클래스 타입(사용자 정의 타입)의 객체에 연산자 사용 

== > 컴파일러가 정의된 함수를 호출

 

ex)

식 : p1+p2

해석 : p1.operator+(p2)

 

ex) 연산자 오버로딩 operator+()의 예시

#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(void)
{
	Point p1(2, 3), p2(5, 5);
	Point p3;

	p3 = p1 + p2;
	p3.Print();
	p3 = p1.operator+(p2);
	p3.Print();
	return 0;
}

 

#const멤버 함수와 비 const멤버 함수

- const 멤버함수 : 객체 내의 멤버 변수를 변경하지 않음을 보장

=> const 객체는 const 멤버 함수만 호출 가능


#단항 연산자 오버로딩

전위 연산 : operator++()

후위 연산 : operator++(int)

	//전위 연산자 오버로딩
	const Point& operator++()
	{
		++x;
		++y;
		return *this;
	}

	//후위 연산자 오버로딩
	const Point operator++(int)
	{
		Point pt(x, y);
		x++;
		y++;
		return pt;

	}
};

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

 

>> 연산자 오버로딩의 방법 2가지

1) 멤버 함수를 이용한 오버로딩

2) 전역함수를 이용한 오버로딩

 

=> 멤버함수를 이용한 오버로딩을 사용하지 못하는 경우가 생김

ex)

상수+객체

=> 상수.operator+(객체)로 해석되지 못함

 

 

>>방법에 따른 해석 2가지

 

ex) p1==p2

1. 멤버 함수 :  p1.operator==(p2)

2. 전역 함수 :  operator==(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() const
	{
		cout << x << ", " << y << endl;
	}
	int GetX() const { return x; }
	int GetY() const { return y; }

	friend const Point& operator-(const Point& p1, const Point& p2);
};

const Point& operator-(const Point& p1, const Point& p2)
{
	return Point(p1.GetX() - p2.GetX(), p1.GetY() - p2.GetY());
}

=> 전역 함수는 private 멤버에 접근 불가

=> 극복방법1. getX, getY 등 getter함수를 이용

=> 극복방법2. friend 선언(모든 클래스 멤버에 접근 가능)

 


#STL에 필요한 주요 연산자 오버로딩

 

>>함수 호출 연산자 오버로딩 : operator()

: 객체를 함수처럼 동작하게 하는 연산자

 

ex) 

#include <iostream>
using namespace std;

struct FuncObject
{
public:
	void operator()(int a )
	{
		cout << "정수 : " << a << endl;
	}

	void operator()(int a ,int b)
	{
		cout << "정수 : " << a <<", "<< b << endl;
	}

	void operator()(int a, int b, int c)
	{
		cout << "정수 : " << a <<", " << b << ", " << c << endl;
	}
};
#키워드 : struct vs class
struct : default가 public
class : default가 private

>>배열 인덱스 연산자 오버로딩 : operator[]

:많은 객체를 저장하고 관리하는 객체(컨테이너 객체)에 사용

 

ex)

- array에서 const 함수 구분이 필요한 이유

int operator[] (int idx) -> 쓰기/읽기

int& operator[] (int idx) const -> const객체가 읽기만 가능

 

#include <iostream>
using namespace std;


class Array
{
private:
	int* arr;
	int size;
	int capacity;
public:
	Array(int cap) : arr(0), size(0), capacity(cap)
	{
		arr = new int[capacity];
	}

	~Array()
	{
		delete[] arr;
	}

	void Add(int data)
	{
		if (size < capacity)
		{
			arr[size++] = data;
		}
	}

	int Size() const
	{
		return size;
	}

	//const 함수
	//그저 읽기 위한 것
	int operator[](int idx) const
	{
		return arr[idx];
	}

	//비 const 함수
	//쓰기용 함수 ->주소값 넘겨줌
	int& operator[](int idx)
	{
		return arr[idx];
	}
};

#메모리 접근, 클래스 멤버 접근 연산자 오버로딩

- operator -> : 포인터 클래스가 일반멤버에 접근하기 위해서

- operator *  : 포인터가 가리키는 객체 반환

 

-> 스마트 포인터나 반복자에 사용

 

ex1) 스마트 포인터 예제 : 객체 소멸시 자동으로 메모리 반환(소멸자 활용)

#include <iostream>

using namespace std;

class Point
{
private:
	int x;
	int y;
public:
	Point(int _x, int _y) : x(_x), y(_y) {};
	void Print()
	{
		cout << x << ", " << y << endl;
	}

};

class Pointptr
{
private:
	Point* ptr;
public:
	Pointptr(Point* p) : ptr(p) {  };

	Point& operator*()
	{
		return *ptr;
	}
	Point* operator->()
	{
		return ptr;
	}
	//자동 메모리 반환
	~Pointptr()
	{
		delete ptr;
	}

};


int main6(void)
{
	Point* p1 = new Point(2, 3);
	Pointptr p2 = new Point(5, 5);

	p1->Print();
	p2->Print();
	cout << endl;

	(*p1).Print();
	(*p2).Print();
	cout << endl;

	delete p1;

	return 0;
}

#타입 변환 연산자 오버로딩

1) 생성자를 이용한 타입변환

2) 타입변환 연산자 오버로딩을 이용한 타입 변환 => 반환타입 미지정 like 생성자, 소멸자

ex) operator int () const ->int로 변환되어야 하는 상황

ex) operator double () const -> double로 변환되어야 하는 상황

 

ex1) 생성자를 이용한 타입변환

=>임시 객체가 형성되어 복사됨

 class B
 {
 public :
	 B() { cout << "B() 생성자 호출" << endl; }
	 B(A& _a) { cout << "B(A &_a) 생성자 호출" << endl; }
	 B(int n) { cout << "B(int n) 생성자 호출" << endl; }
	 B(double d) { cout << "B(double d) 생성자 호출" << endl; }


 };

 

=>만약 생성자를 이용한 형변환을 의도하지 않는다면?

=> explicit키워드 = 명시적 호출만 가능

#include <iostream>

using namespace std;

class Point
{
private:
	int x;
    int y;
    
public:
	explicit Point(int _x, int _y) : x(_x), y(_y){};
	void Print() const { cout<< x << ", " << y << endl;}
}

=> Point p1= Point(10) // 이건 가능

=> Point p1= 10 //이건 explicit 때문에 불가능

 

 

ex2) 타입변환 연산자를 이용한 변환

#include <iostream>

using namespace std;

class A
{};

class B
{
	int x;
public:
	B() { cout << "B() 생성자 호출" << endl; }
	B(A& _a) { cout << "B(A &_a) 생성자 호출" << endl; }
	B(int n): x(n) { cout << "B(int n) 생성자 호출" << endl; }
	B(double d) { cout << "B(double d) 생성자 호출" << endl; }

	//타입 변환 연산자
	operator int() const
	{
		return 3;
	}

	operator A() const
	{
		cout << "operator A() 호출" << endl;
		return A();//임시 객체 반환
	}

	operator double() const
	{
		cout << "operator double()호출" << endl;
		return 5.5;
	}




};

int main(void)
{
	A a;
	int n = 10;
	double d = 5.5;

	B b;
	b = a; // B(A a)가 형성 후 복사생성자 호출
	b = n; // B(int n) 가 형성 후 복사생성자 호출
	b = d; // B(double d)가 형성 후 복사성자 호출

	a = b; // b.operator A()
	n = b; // b.operator int()
	d = b; // b.operator double()
	cout << n << endl;
	cout << d << endl;

	return 0;


}

 

=>결과