따라하며 배우는 C++
9. 연산자 오버로딩
9.1 산술 연산자 오버로딩 하기
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
};
Cents add(const Cents& c1, const Cents& c2)
{
return Cents(c1.getCents() + c2.getCents());
}
int main()
{
Cents cents1(6);
Cents cents2(8);
Cents sum = add(cents1, cents2);
cout << sum.getCents() << endl;
return 0;
}
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
};
Cents operator + (const Cents& c1, const Cents& c2)
{
return Cents(c1.getCents() + c2.getCents());
}
int main()
{
Cents cents1(6);
Cents cents2(8);
cout << (cents1 + cents2 + Cents(6)).getCents() << endl;
return 0;
}
이렇게 하여 산술 연산자도 오버로딩 가능!
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
friend Cents operator + (const Cents& c1, const Cents& c2);
{
return Cents(c1.getCents() + c2.getCents());
}
};
int main()
{
Cents cents1(6);
Cents cents2(8);
cout << (cents1 + cents2 + Cents(6) + Cents(10)).getCents() << endl;
return 0;
}
//여기서 .(멤버 선택), .* (멤버 포인터 선택), sizeof, ::, 삼항 연산자는 오버로딩이 안됨.
//연산자의 우선순위는 바꾸지 못한다.
bitwise XOR도 애매하기 때문에 오버로딩 안하는 편이 좋다.
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
Cents operator + (const Cents& c2);
{
return Cents(this->m_cents + c2.m_cents);
}
};
멤버 함수로 바꾸면 위와 같이 해야 한다.
멤버로만 해야 하는 연산자도 있다. =, [...]//어레이, (), ->
9.2 입출력 연산자 오버로딩 하기
#include <iostream>
using namespace std;
class Point
{
private:
double m_x, m_y, m_z;
public:
Point(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{ }
double getX() { return m_x; }
double getY() { return m_y; }
double getZ() { return m_z; }
void print()
{
cout << m_x << " " << m_y << " " << m_z << endl;
}
};
int main()
{
Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
p1.print();
cout << " ";
p2.print();
cout << endl;
return 0;
}
#include <iostream>
using namespace std;
class Point
{
private:
double m_x, m_y, m_z;
public:
Point(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{ }
double getX() { return m_x; }
double getY() { return m_y; }
double getZ() { return m_z; }
//이건 멤버 function으로 만들 수 없다.
//첫번째 매개변수가 Point가 아닌 ostream이라서 그러하다.
friend ostream& operator << (ostream& out, const Point& point)
{
out << point.m_x << " " << point.m_y << " " << point.m_z;
return out;
}
};
int main()
{
Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
cout << p1 << endl;
return 0;
}
#include <iostream>
#include <fstream>
using namespace std;
class Point
{
private:
double m_x, m_y, m_z;
public:
Point(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{ }
double getX() { return m_x; }
double getY() { return m_y; }
double getZ() { return m_z; }
//이건 멤버 function으로 만들 수 없다.
//첫번째 매개변수가 Point가 아닌 ostream이라서 그러하다.
friend ostream& operator << (ostream& out, const Point& point)
{
out << point.m_x << " " << point.m_y << " " << point.m_z;
return out;
}
};
int main()
{
ofstream of("out.txt");
Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
cout << p1 << endl;
of << p1 << endl;
of.close();
return 0;
}
>> 하나로 파일에도 출력이 가능하다.
friend istream& operator >> (istream& in, Point& point)
{
in >> point.m_x >> point.m_y >> point.m_z;
return in;
}
입력은 이렇게 구현 가능
9.3 단항 연산자 오버로딩 하기
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
Cents operator + (const Cents& c2)
{
return Cents(this->m_cents + c2.m_cents);
}
Cents operator - () const
{
return Cents(-m_cents);
}
friend std::ostream& operator<<(std::ostream& out, const Cents& cents)
{
out << cents.m_cents;
return out;
}
bool operator ! () const
{
return (m_cents == 0) ? true; false;
}
};
int main()
{
Cents cents1(6);
Cents cents2(0);
cout << cents1 << endl;
cout << -cents1 << endl;
cout << -Cents(-10) << endl;
cout << !cents1 << " " << cents2 << endl;
return 0;
}
9.4 비교 연산자 오버로딩 하기.
sort를 하기 위해선 꼭 오버로딩 되어 있어야 한다!
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
friend bool operator == (const Cents& c1, const Cents& c2)
{
return c1.m_cents == c2.m_cents;
}
friend std::ostream& operator<<(std::ostream& out, const Cents& cents)
{
out << cents.m_cents;
return out;
}
bool operator ! () const
{
return (m_cents == 0) ? true: false;
}
};
int main()
{
Cents ct1(6);
Cents ct2(6);
if (ct1 == ct2) cout << "Euqal!" << endl;
return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
friend bool operator < (const Cents& c1, const Cents& c2)
{
return c1.m_cents < c2.m_cents;
}
friend std::ostream& operator<<(std::ostream& out, const Cents& cents)
{
out << cents.m_cents;
return out;
}
};
int main()
{
vector<Cents> arr(20);
for (unsigned i = 0; i < 20; ++i)
arr[i].getCents() = i;
random_shuffle(begin(arr), end(arr));
for (auto& e : arr) cout << e << " ";
cout << endl;
sort(begin(arr), end(arr));
return 0;
}
9.5 증감 연산자 오버로딩 하기.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Digit
{
private:
int m_digit;
public:
Digit(int digit = 0) : m_digit(digit) {}
//prefix
Digit& operator ++ ()
{
++m_digit;
return *this;
}
//postfix
Digit operator ++ (int)
{
Digit temp(m_digit);
++(*this);
return temp;
}
friend ostream& operator<<(ostream& out, const Digit& d)
{
out << d.m_digit;
return out;
}
};
int main()
{
Digit d(5);
cout << ++d << endl;
cout << d << endl;
cout << d++ << endl;
cout << d << endl;
return 0;
}
9.6 첨자 연산자 오버로딩 하기
[] subscript operator
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class IntList
{
private:
int m_list[10];
public:
void setItem(int index, int value)
{
m_list[index] = value;
}
int getItem(int index)
{
return m_list[index];
}
};
int main()
{
IntList my_list;
my_list.setItem(3, 1);
cout << my_list.getItem(3) << endl;
return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class IntList
{
private:
int m_list[10];
public:
void setItem(int index, int value)
{
m_list[index] = value;
}
int getItem(int index)
{
return m_list[index];
}
int* getList()
{
return m_list;
}
};
int main()
{
IntList my_list;
my_list.setItem(3, 1);
cout << my_list.getItem(3) << endl;
my_list.getList()[3] = 1;
cout << my_list.getList()[3] << endl;
return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class IntList
{
private:
int m_list[10];
public:
int& operator[] (const int index)
{
return m_list[index];
}
};
int main()
{
IntList my_list;
my_list[3] = 10;
cout << my_list[3] << endl;
return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class IntList
{
private:
int m_list[10];
public:
int& operator[] (const int index)
{
return m_list[index];
}
const int& operator[] (const int index) const
{
return m_list[index];
}
};
int main()
{
const IntList my_list;
//my_list[3] = 10;
cout << my_list[3] << endl;
return 0;
}
위는 const일 경우
#include <iostream>
#include <cassert>
using namespace std;
class IntList
{
private:
int m_list[10];
public:
int& operator[] (const int index)
{
assert(index >= 0);
assert(index < 10);
return m_list[index];
}
const int& operator[] (const int index) const
{
assert(index >= 0);
assert(index < 10);
return m_list[index];
}
};
assert로 오류를 미연에 방지할 수 있다.
#include <iostream>
#include <cassert>
using namespace std;
class IntList
{
private:
int m_list[10];
public:
int& operator[] (const int index)
{
assert(index >= 0);
assert(index < 10);
return m_list[index];
}
const int& operator[] (const int index) const
{
assert(index >= 0);
assert(index < 10);
return m_list[index];
}
};
int main()
{
IntList* list = new IntList;
(*list)[3] = 10;
list[3] = IntList; //의도가 달라진다.
return 0;
}
9.7 괄호 연산자 오버로딩과 함수 객체
parenthesis, Function object(Functor)
#include <iostream>
using namespace std;
class Accumulator
{
private:
int m_counter = 0;
public:
int operator() (int i) { return (m_counter += i); }
};
int main()
{
Accumulator acc;
cout << acc(10) << endl;
cout << acc(20) << endl;
return 0;
}
이런 식으로 함수처럼 호출하는 것 자체를 Functor라고 부른다.
9.8 형변환static_cast (=typecasts) 오버로딩 하기
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0)
{
m_cents = cents;
}
int getCents() { return m_cents; }
void setCents(int cents) { m_cents = cents; }
operator int()
{
cout << "cast here" << endl;
return m_cents;
}
};
void printInt(const int& value)
{
cout << value << endl;
}
int main()
{
Cents cents(7);
printInt(cents);
return 0;
}
#include <iostream>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0)
{
m_cents = cents;
}
int getCents() { return m_cents; }
void setCents(int cents) { m_cents = cents; }
operator int()
{
cout << "cast here" << endl;
return m_cents;
}
};
void printInt(const int& value)
{
cout << value << endl;
}
class Dollar
{
private:
int m_dollars = 0;
public:
Dollar(const int& input) : m_dollars(input) {}
operator Cents()
{
return Cents(m_dollars *100);
}
};
int main()
{
Dollar dol(2);
Cents cents = dol;
printInt(cents);
return 0;
}
9.9 복사 생성자, 복사 초기화, 반환값 최적화 RVO(Return Value Optimization)
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
Fraction(const Fraction& fraction) //copy constructor
: m_numerator(fraction.m_denominator), m_denominator(fraction.m_denominator)
{
cout << "Copy constroctor called" << endl;
}
friend ostream& operator << (ostream& out, const Fraction& f)
{
out << f.m_numerator << "/" << f.m_denominator << endl;
return out;
}
};
int main()
{
Fraction frac(3, 5);
Fraction fr_copy(frac); //Fraction fr_copy = frac의 경우도 가능
cout << frac << " " << fr_copy << endl;
return 0;
}
//가끔 보안을 중요시하는 경우 copy constructor를 private에 넣기도 한다.
//Fraction fr_copy(Fraction(3, 10))의 경우 최적화를 통해 복사 생성자를 없애기도 한다.
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
Fraction(const Fraction& fraction) //copy constructor
: m_numerator(fraction.m_denominator), m_denominator(fraction.m_denominator)
{
cout << "Copy constroctor called" << endl;
}
friend ostream& operator << (ostream& out, const Fraction& f)
{
out << f.m_numerator << "/" << f.m_denominator << endl;
return out;
}
};
Fraction doSomething()
{
Fraction temp(1, 2);
return temp;
}
int main()
{
Fraction result = doSomething();
cout << result << endl;
return 0;
}
위의 케이스에서 release 모드에선 복사 생성자가 생기지 않는다.
함수를 벗어나면 해당 return 값이 의미가 없어지기 때문에 컴파일러가 알아서 변수에 담아버린다.
9.10 변환 생성자 Converting constructor, explicit, delete
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
Fraction(const Fraction& fraction) //copy constructor
: m_numerator(fraction.m_denominator), m_denominator(fraction.m_denominator)
{
cout << "Copy constroctor called" << endl;
}
friend ostream& operator << (ostream& out, const Fraction& f)
{
out << f.m_numerator << "/" << f.m_denominator << endl;
return out;
}
};
void doSomething(Fraction frac)
{
cout << frac << endl;
}
int main()
{
Fraction frac(7);
doSomething(7); //이것도 가능.
//어떤 값이 들어왔을 때 매개변수에서 형 변환을 할 것이 정해져 있다면
//알아서 생성자의 인수로 넣어주는 것. = 변환 생성자.
return 0;
}
그러나 생성자 앞에 "explicit" 키워드를 쓰면 형 변환을 막아버린다.
explicit Fraction(int num = 0, int den = 1)
//
Fraction frac(10);
doSomething(frac);
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(char) = delete; //특정 타입의 생성자를 사용하지 않을 수 있다.
Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
Fraction(const Fraction& fraction) //copy constructor
: m_numerator(fraction.m_denominator), m_denominator(fraction.m_denominator)
{
cout << "Copy constroctor called" << endl;
}
friend ostream& operator << (ostream& out, const Fraction& f)
{
out << f.m_numerator << "/" << f.m_denominator << endl;
return out;
}
};
void doSomething(Fraction frac)
{
cout << frac << endl;
}
int main()
{
//Fraction frac2('a'); //불가
return 0;
}
9.11 대입 연산자 오버로딩, 깊은 복사, 얕은 복사. (Deep copy vs Shallow copy)
#include <iostream>
#include <cassert>
using namespace std;
class MyString
{
public:
char* m_data = nullptr;
int m_length = 0;
public:
MyString(const char* source = "")
{
assert(source);
m_length = strlen(source) + 1; //글자 수 + null 캐릭터
m_data = new char[m_length]; //동적 할당
for (int i = 0; i < m_length; ++i)
m_data[i] = source[i];
m_data[m_length - 1] = '\0';
}
~MyString()
{
delete[] m_data;
}
int getLength() { return m_length; }
char* getString() { return m_data; }
};
int main()
{
MyString hello("Hello");
cout << (int*)hello.m_data << endl;
cout << hello.getString() << endl;
{
MyString copy = hello; //복사 생성자가 호출됨.
//복사 생성자가 hello의 주소를 복사하여 copy에 넣어줌.
//즉 copy에는 hello의 주소가 복사되기만 한 것이기 때문에
//"두" MyString의 m_data에 대해 할당된 메모리가 "하나"인 것이다.
cout << (int*)copy.m_data << endl;
cout << copy.getString() << endl;
//소멸자가 있으므로 scope를 벗어나면 copy의 메모리가 해제된다.
//그 과정에서 같은 곳을 가리키고 있었던 hello의 메모리도 강제로 해제되어 버리는 것.
}
cout << hello.getString() << endl;
return 0;
}
포인터에서도 주소 값만 복사해 넣어주는 것을 얕은 복사라고 한다(Shallow Copy)
그리고 주소 값을 복사하는 대신 메모리를 다시 할당 받고, 값을 하나하나 복사받는 것을 깊은 복사라고 한다.
#include <iostream>
#include <cassert>
using namespace std;
class MyString
{
public:
char* m_data = nullptr;
int m_length = 0;
public:
MyString(const char* source = "")
{
assert(source);
m_length = strlen(source) + 1; //글자 수 + null 캐릭터
m_data = new char[m_length]; //동적 할당
for (int i = 0; i < m_length; ++i)
m_data[i] = source[i];
m_data[m_length - 1] = '\0';
}
//copy constructor
MyString(const MyString &source)
{
cout << "Copy constructor " << endl;
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new char[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
}
//= operator 오버로딩
MyString& operator = (const MyString& source)
{
//default copy constructor는 아래 주석과 같은 내용으로 되어 있다.
//this->m_data = source.m_data;
//this->m_length = source.m_length;
//여기서 포인터일 경우 그 주소만 복사가 되는 것.
cout << "Assignment operator " << endl;
if (this == &source) //prevent self-assignment
return *this;
//복사 생성자의 경우는 이런 경우가 생기지 않지만
//=에서는 이런 문제가 생길 수 있다.
//주소를 비교해 같다면, 자신의 값을 리턴해버리면 된다.
delete[] m_data; //원래 있던 string을 지워버리고
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new char[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
} //값들을 다시 할당받는다.
else
m_data = nullptr;
return *this;
}
//desctuctor
~MyString()
{
delete[] m_data;
}
int getLength() { return m_length; }
char* getString() { return m_data; }
};
int main()
{
MyString hello("Hello");
cout << (int*)hello.m_data << endl;
cout << hello.getString() << endl;
{
MyString copy = hello; //복사 생성자가 호출됨.
cout << (int*)copy.m_data << endl;
cout << copy.getString() << endl;
}
cout << hello.getString() << endl;
return 0;
}
int main()
{
MyString hello("hello");
MyString str = hello; //이 경우 str이 새로 생성되는 객체지만
//copy constructor가 호출됨.
MyString str2;
str2 = hello; //이럴 경우 assignment
return 0;
}
//얕은 복사를 막기 위해 차선책은 그냥 막아버리는 것.
//물론 깊은 복사를 구체적으로 구현하는 것이 제일 좋다
MyString(const MyString &source) = delete;
9.12 이니셜라이저 리스트 initializer list
#include <iostream>
#include <cassert>
#include <initializer_list>
using namespace std;
class IntArray
{
private:
unsigned m_length = 0;
int* m_data = nullptr;
public:
IntArray(unsigned length)
:m_length(length)
{
m_data = new int[length];
}
IntArray(const initializer_list<int>& list)
:IntArray(list.size())
{
int count = 0;
for (auto& element : list)
{
m_data[count] = element;
++count;
}
/* for (unsigned count = 0; count < list.size(); ++count)
m_data[count] = list[count];*/
//이니셜라이저 리스트는 []를 제공하지 않는다.
}
~IntArray()
{
delete[] this->m_data;
}
friend ostream& operator << (ostream& out, IntArray& arr)
{
for(unsigned i = 0; i< arr.m_length ; ++i)
out << arr.m_data[i] << " ";
out << endl;
return out;
}
};
int main()
{
int my_arr1[5] = { 1, 2, 3, 4, 5 }; //기본 자료형은 이렇게 초기화 리스트를
//편하게 사용할 수 있다. 정적이건(위) 동적이건(아래)
int* my_arr = new int[5]{ 1,2, 3, 4,5 };
auto il = { 10, 20, 30 }; //initializer list를 인클루드하면
//자동으로 형을 잡아준다.
IntArray int_array{ 1, 2, 3, 4, 5 };
cout << int_array << endl;
IntArray int_array2 = { 1, 2, 3, 4, 5 };
cout << int_array2 << endl;
return 0;
}
'개발 공부 > C++' 카테고리의 다른 글
따라하며 배우는 C++ 11. 상속 (0) | 2020.07.02 |
---|---|
따라하며 배우는 C++ 10. 객체 사이의 관계들에 대해 (0) | 2020.07.02 |
따라하며 배우는 C++ 8. 객체지향의 기초 (0) | 2020.07.01 |
따라하며 배우는 C++ 7. 함수 (0) | 2020.07.01 |
따라하며 배우는 C++ 6. 행렬, 문자열, 포인터, 참조 (0) | 2020.06.30 |