이번 강좌에서는
산술 연산자 오버로딩
비교 연산자 오버로딩
대입 연산자 오버로딩
if(str1.compare(str2) == 0) 하지 말고 if(str1 == str2) 하면 어떨까?
str1.insert(str1.length(), str2) 하지 말고 str1 = str1 + str2; 하면 어떨까?
C 언어 에선 연사자들은 모두 기본적으로 정의 되어있는 데이터 타입 (int, float 등) 으로 정의 되어 있어서
이들을 구초체 변수에 사용하는건 불가능 했다.
But
C++ 에선 ::
(범위 지정), .
(멤버 지정), .*
(멤버 포인터로 멤버 지정) 을 제외한 연산자를 오버로딩(overloading) 할수 있다.
+, -, * 와 같은 산술 연산자
+=, -= 와 같은 축약형 연산자
>=, == 와 같은 비교 연산자
&&, || 와 같은 논리 연산자
-> 나 와 같은 멤버 선택 연산자 (여기서 는 역참조 연산자 입니다. 포인터에서 *p 할 때 처럼)
++, -- 증감 연산자
[] (배열 연산자) 와 심지어 () 까지 (함수 호출 연산자)
일단 오버로딩 원하는 연산자 함수를 제작.
(리턴타입) 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;
}
std::complex 가 정의 되어 있다. 여기선 교육용 이기 때문에 ~
복소수란
(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 연산자 오버로딩을 이용해서
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))
이러한 문제를 막기 위해선 반드시 사칙 연산의 경우 반드시 값을 리턴해야만(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 는 아무런 영향 없음
부턴 일단 패스~