모두의 코드 : 씹어먹는 C++ - <5 - 1. 내가 만든 연산자 - 연산자 오버로딩>

YP J·2022년 6월 17일
0

모두의코드 C++

목록 보기
3/11

이번 강좌에서는

  • 산술 연산자 오버로딩

  • 비교 연산자 오버로딩

  • 대입 연산자 오버로딩

if(str1.compare(str2) == 0) 하지 말고 if(str1 == str2) 하면 어떨까?

str1.insert(str1.length(), str2) 하지 말고 str1 = str1 + str2; 하면 어떨까?

C 언어 에선 연사자들은 모두 기본적으로 정의 되어있는 데이터 타입 (int, float 등) 으로 정의 되어 있어서
이들을 구초체 변수에 사용하는건 불가능 했다.

But
C++ 에선 :: (범위 지정), . (멤버 지정), .* (멤버 포인터로 멤버 지정) 을 제외한 연산자를 오버로딩(overloading) 할수 있다.

  • +, -, * 와 같은 산술 연산자

  • +=, -= 와 같은 축약형 연산자

  • >=, == 와 같은 비교 연산자

  • &&, || 와 같은 논리 연산자

  • -> 나 와 같은 멤버 선택 연산자 (여기서 는 역참조 연산자 입니다. 포인터에서 *p 할 때 처럼)

  • ++, -- 증감 연산자

  • [] (배열 연산자) 와 심지어 () 까지 (함수 호출 연산자)

myString 의 '==' 연산자 오버로딩

일단 오버로딩 원하는 연산자 함수를 제작.
(리턴타입) operator(연산자) (연산자가 받는 인자)

위방법 외에는 함수이름으로 연산자를 절대 넣을수 없다.

bool operator==(myString& str);

이제 str1 == str2 명령 하면
str1.operator==(str2) 로 변환되어서 처리된다.
그리고 그 결과값을 리턴한다.

bool myString::operator==(myString& str)
{
	return !compare(str); // str과 같으면 compare에서 0 리턴 한다.
}

!compare(str) 을 리턴하는 이유는
compare함수에서 str 과 *this 가 같으면 0을 리턴 하는데
operator== 은 둘이 같으면 true를 리턴 해야하기 때문

코드

#include <iostream>
#include <string>

class myString
{
	private:
		char* string_content;	// 문자열 데이터를 가리키는 포인터
		int string_len;      // 문자열 길이

		int memory_capacity; //현재 할당된 용량
	public:
		myString(char c); // 문자 하나로 생성
		myString(const char *str); // 문자열로 부터 생성
		myString(const myString& str); // 복사생성자
		~myString();
		//
		int len() const;
		int capacity() const;
		void reserve(int size);

		void print() const;
		void println() const;

		char at(int i) const;

		int compare(myString& str);       // 인자에 클래스 변수를 주석으로 넣어주는 의미 ?? 정확히 모름 -> 아마 복사생성해서 넣는다는 의미?
		bool operator==(myString& str);
};

////


myString::myString(char c)
{
	string_content = new char[1];
	string_content[0] = c;
	memory_capacity = 1;
	string_len = 1;
}

myString::myString(const char *str)
{
	string_len = strlen(str);
	//std::cout << "TEST----- : ";
	//std::cout << string_len << std::endl;
	memory_capacity = string_len;
	string_content = new char[string_len];

	for (int i=0; i != string_len; i++)
		string_content[i]=str[i];
}

myString::myString(const myString& str)
{
	string_len = str.string_len;
	string_content = new char[string_len];
	for (int i=0; i!= string_len; i++)
		string_content[i]=str.string_content[i];}

myString::~myString()
{
	delete[] string_content;
}


////

int myString::len() const {return string_len;}

void myString::print() const
{
	for (int i=0; i!= string_len; i++)
	{
		std::cout<< string_content[i];
	}
}

void myString::println() const
{
	for (int i=0; i!= string_len; i++)
	{
		std::cout<< string_content[i];
	}
	std::cout << std::endl;
}

int myString::capacity() const
{
	return memory_capacity;
}

void myString::reserve(int size)
{
	if (size > memory_capacity)
	{
		char *prev_string_content = string_content;

		string_content = new char[size];
		memory_capacity = size;

		for (int i=0; i!= string_len; i++)
			string_content[i] = prev_string_content[i];
		delete[] prev_string_content;
	}
	// 만일 예약하려하는  size가 현재 capacity 보다 작다면
	// 아무것도 안 해도 된다.
}

char myString::at(int i) const
{
	if (i>= string_len || i < 0)
		return 0;
	else
		return string_content[i];
}

int myString::compare(myString& str)
{
	// (*this) - (str) 을 수행해서 1,0,-1 로 그결과를 리턴한다.
	// 1은 (*this)가 사전식으로 더 뒤에 온다는 의미.
	// 0은 두 문자열이 닽다는 의미

	for (int i=0; i< std::min(string_len, str.string_len); i++)
	{
		if (string_content[i] > str.string_content[i])
			return 1;
		else if (string_content[i] < str.string_content[i])
			return -1;
	}
	// 여기 까지 했는데 끝나지 않았다면 앞 부분 까지 모두 같다는 것.
	// 만일 문자열 길이가 같다면 두 문자열은 아예 같은 문자열이 된다.
	if (string_len == str.string_len)
		return 0;

	// abc , abcd의 크기는 abcd가 뒤에 오게 된다.
	else if (string_len > str.string_len)
		return 1;

	return -1;
}

bool myString::operator==(myString& str)
{
	return !compare(str);
}

int main()
{
	myString str1("a word");
	myString str2("aaaaaa");
	myString str3 = str2;

	if (str1 == str2)
		std::cout << "str1 str2 same" << std::endl;
	else
		std::cout << "str1 str2 diff" << std::endl;

	if (str2 == str3)
		std::cout << "str2 str3 same"<< std::endl;
	else
		std::cout << "str2 str3 diff"<<std::endl;
}

복소수 complex number 클래스 만들기

std::complex 가 정의 되어 있다. 여기선 교육용 이기 때문에 ~

복소수란

z=a+biz=a+bi (a,b 는 모두 실수)

#include <iostream>
#include <string>

class Complex
{
	private:
		double _real, _img;
	public:
		Complex(double real, double img);
};

Complex::Complex(double real, double img):_real(real),_img(img){}

#include <iostream>
#include <string>

class Complex
{
	private:
		double _real, _img;
	public:
		Complex(double real, double img);

		Complex plus(const Complex& c);
		Complex minus(const Complex& c);
		Complex times(const Complex& c);
		Complex div(const Complex& c);

};

Complex::Complex(double real, double img):_real(real),_img(img){}

로 정의 되어있다면, 만일 int 형 변수 였다면

a + b / c + d;

a.plus(b.div(c)).plus(d); 

와 같이 복잡해지고 가독성이 떨어진다.

but 연산자 오버로딩을 이용해서

  • plus 를 operaotr+
  • div -> operaotr/
    ...로 오버로딩하면

a + b/c + d; 를 컴파일러가 알아서
a.operator+(b.operator/(c)).operator+(d);
로 변환 시켜서 처리하기 때문에 속도나 다른 어떠한 차이 없이 가독성 + 편리한을 얻을수 있다.

코드

#include <iostream>
#include <string>

class Complex
{
	private:
		double _real, _img;
	public:
		Complex(double real, double img)
			: _real(real), _img(img) {}
		Complex(const Complex& c)
		{
			_real = c._real;
			_img = c._img;
		}

		Complex operator+(const Complex& c) const;
		Complex operator-(const Complex& c) const;
		Complex operator*(const Complex& c) const;
		Complex operator/(const Complex& c) const;

		void print()
		{
			std::cout << "( " << _real << " , " << _img << " )" << std:: endl;
		}
};


Complex	Complex::operator+(const Complex& c) const
{
	Complex temp(_real +c._real, _img+c._img);
	return temp;
}

Complex Complex::operator-(const Complex& c)const
{
	Complex temp(_real-c._real, _img-c._img);
	return temp;
}

Complex Complex::operator*(const Complex& c) const {
  Complex temp(_real * c._real - _img * c._img, _real * c._img + _img * c._real);
  return temp;
}

Complex Complex::operator/(const Complex& c) const {
  Complex temp(
    (_real * c._real + _img * c._img) / (c._real * c._real + c._img * c._img),
    (_img * c._real - _real * c._img) / (c._real * c._real + c._img * c._img));
  return temp;
}

int main()
{
	Complex a(1.0, 2.0);
	Complex b(3.0, -2.0);

	Complex c = a*b;

	c.print();
}
>> ( 7 , 4 )
// Complex를 리턴 하면 값의 복사가 일어나기 때문에 속도 저하 발생.
  Complex operator+(const Complex& c) const;
  Complex operator-(const Complex& c) const;
  Complex operator*(const Complex& c) const;
  Complex operator/(const Complex& c) const;

위 4개의 연산자함수 모두 Complex& 가 아닌 Complex를 리턴하고 있다.

// 레퍼런스를 리턴 ( 값의 복사 대신 레퍼런스만 복사됨)
Complex& operator+(const Complex& c)
{
	_real+=c._real;
    _img+=c._img;
    return *this;
}

위의 경우는 잘못 설계한 것!!
물론 이렇게 설계 했을 경우
Complex 를 리턴 하는 연산자 함수는 값의 복사가 일어나기 때문에 속도 저하가 발생하지만
위의 경우처럼 레퍼런스를 리턴하게 되면 값의 복사 대신 레퍼런스만 복사하는 것이므로 큰 속도의 저하는 나타나지 않는다

*하지만 위와 같이 operate+ 를 정의할경우 문제를 보자.

Complex a = b+ c + b;

우리의 의도는 a = 2 * b * c 이다.
하지만 참조자를 리턴하는 경우 처리되는 것을보면
(b.plus(c).plub(b))

  • b 에다 c 더하고 b.plus(c) : b에 b + c 가 들어간다.
  • 그리고 거기에 다시 plus(b) 를 하게 되면
  • (b + c ) + ( b + c) 가 된다
  • 왜냐면 현재 b 에는 b+c 가 들어있으닌까
  • a = 2b + 2 c 가 된다.

이러한 문제를 막기 위해선 반드시 사칙 연산의 경우 반드시 값을 리턴해야만(Complex 리턴) 한다.

또한 함수 내부에서 읽기만 수행되고 값이 바뀌지 않은 인자들에 대해서는 const 키워드를 붙여주는 것이 바람직하다.
Complex operator/(const Complex& c) const;
여기서 c의 값을 읽기만 하지 c값에 어떠한 변화도 주지 않으므로 const Complex& 타입으로 인자를 받는다.

인자의 값이 함수 내부에서 바뀌지 않는다고 확신할 때는 const 키워드를 붙여주는게 나중에 실수를 줄여준다.
상수 함수로 선언할수 있는경우 상수 함수로 선언하는 것이 굳.

대입 연산자 함수

Complex& operator=(const Complex& c);

기본적으로 대입 연산자 함수는 자기 자신을 가리키는 레퍼런스를 리턴한다(Complex& )
왜 냐면
a = b = c;
b = c; 가 b 를 리턴 해야지, a = b; 가 성공적으로 수행될 수 있기 때문이다.

이때 Complex 타입을 리턴 하지 않고 굳이 Complex& 타입을 리턴하는 이유는 대입 연산이후에 불필요한 복사를 방지하기 위해서이다.

Complex& Complex::operator=(const Complex& c)
{
	_real = c._real;
    _img = c._img;
    return *this;
}

total code

#include <iostream>
#include <string>

class Complex
{
	private:
		double _real, _img;
	public:
		Complex(double real, double img)
			: _real(real), _img(img) {}
		Complex(const Complex& c)
		{
			_real = c._real;
			_img = c._img;
		}

		Complex operator+(const Complex& c) const;
		Complex operator-(const Complex& c) const;
		Complex operator*(const Complex& c) const;
		Complex operator/(const Complex& c) const;

		Complex& operator=(const Complex& c);

		void print()
		{
			std::cout << "( " << _real << " , " << _img << " )" << std:: endl;
		}
};

Complex& Complex::operator=(const Complex& c)
{
	_real = c._real;
	_img = c._img;
	return *this;
}

Complex	Complex::operator+(const Complex& c) const
{
	Complex temp(_real +c._real, _img+c._img);
	return temp;
}

Complex Complex::operator-(const Complex& c)const
{
	Complex temp(_real-c._real, _img-c._img);
	return temp;
}

Complex Complex::operator*(const Complex& c) const {
  Complex temp(_real * c._real - _img * c._img, _real * c._img + _img * c._real);
  return temp;
}

Complex Complex::operator/(const Complex& c) const {
  Complex temp(
    (_real * c._real + _img * c._img) / (c._real * c._real + c._img * c._img),
    (_img * c._real - _real * c._img) / (c._real * c._real + c._img * c._img));
  return temp;
}

int main()
{
	Complex a(1.0, 2.0);
	Complex b(3.0, -2.0);
	Complex d(0.0, 0.0);
	Complex c = a*b;

	d = a*b+a / b + a + b;

	d.print();
}
>> ( 10.9231 , 4.61538 )

그런데 operator= 를 만들지 않더라도 컴파일 하면 잘 작동한다.
컴파일 차원에서 디폴트대입연산자(default assignment operator) 를 지원하고 있기 때문이다.

디폴트 복사 생성자와 마찬가지로 디폴트 대입 연산자 역시 얕은 복사를 수행한다.
따라서 깊은 복사가 필요한 클래스의 경우
( 예를 들어 , 클래스 내부적으로 동적으로 할당되는 메모리를 관리하는 포인터가 있다던지) 대입 연산자 함수를 꼭 만들어 주어야할 필요가 있다.

Some_Class a = b; vs Some_class a; "\n"; a = b;

전자: 아예 a 의 복사 생성자 가 호출 되는것
후자: a의 그냥 기본 생성자가 호출 된 다음 다음 문장에서 대입 연산자 함수가 실행

마찬가지로 +=, -= ,*=, /= 도 구현 할수 있다.
이번엔 미리 만들어 놓은 =,+,- ... 등을 이용하면 된다.

Complex& Complex::operator+=(const Complex& c) {
  (*this) = (*this) + c;
  return *this;
}
Complex& Complex::operator-=(const Complex& c) {
  (*this) = (*this) - c;
  return *this;
}
Complex& Complex::operator*=(const Complex& c) {
  (*this) = (*this) * c;
  return *this;
}
Complex& Complex::operator/=(const Complex& c) {
  (*this) = (*this) / c;
  return *this;
}

객체 내부의 상태를 변경하기 때문에 상수 함수로 선언 할수 없다.

total code

#include <iostream>
#include <string>

class Complex
{
	private:
		double _real, _img;
	public:
		Complex(double real, double img)
			: _real(real), _img(img) {}
		Complex(const Complex& c)
		{
			_real = c._real;
			_img = c._img;
		}

		Complex operator+(const Complex& c) const;
		Complex operator-(const Complex& c) const;
		Complex operator*(const Complex& c) const;
		Complex operator/(const Complex& c) const;

		Complex& operator=(const Complex& c);

		Complex& operator+=(const Complex& c);
		Complex& operator-=(const Complex& c);
		Complex& operator*=(const Complex& c);
		Complex& operator/=(const Complex& c);
		void print()
		{
			std::cout << "( " << _real << " , " << _img << " )" << std:: endl;
		}
};

Complex& Complex::operator=(const Complex& c)
{
	_real = c._real;
	_img = c._img;
	return *this;
}

Complex	Complex::operator+(const Complex& c) const
{
	Complex temp(_real +c._real, _img+c._img);
	return temp;
}

Complex Complex::operator-(const Complex& c)const
{
	Complex temp(_real-c._real, _img-c._img);
	return temp;
}

Complex Complex::operator*(const Complex& c) const {
  Complex temp(_real * c._real - _img * c._img, _real * c._img + _img * c._real);
  return temp;
}

Complex Complex::operator/(const Complex& c) const {
  Complex temp(
    (_real * c._real + _img * c._img) / (c._real * c._real + c._img * c._img),
    (_img * c._real - _real * c._img) / (c._real * c._real + c._img * c._img));
  return temp;
}

Complex& Complex::operator+=(const Complex& c)
{
	(*this) = (*this) + c;
	return (*this);
}

Complex& Complex::operator-=(const Complex& c)
{
	(*this) = (*this) - c;
	return (*this);
}

Complex& Complex::operator*=(const Complex& c)
{
	(*this) = (*this) * c;
	return (*this);
}

Complex& Complex::operator/=(const Complex& c)
{
	(*this) = (*this) / c;
	return (*this);
}

int main()
{
	Complex a(1.0, 2.0);
	Complex b(3.0, -2.0);
	Complex d(0.0, 0.0);
	Complex c = a*b;

	d = a*b*a / b + a + b;
	d.print();
	d += a;

	d.print();
    a.print();

}
>> ( 1 , 4 )
>> ( 2 , 6 )
>> ( 1 , 2 ) 

d 값만 바뀌고 a 는 아무런 영향 없음

문자열로 'Complex'수와 덧셈하기

부턴 일단 패스~

profile
be pro

0개의 댓글