![article thumbnail image](https://blog.kakaocdn.net/dn/ofEIG/btqEj80kL4X/6qCJlkJ1jlLTRXSKYUG1p1/img.png)
반응형
산술 연산자 오버로딩 하기
#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());
//}
// friend 없애고 멤버 함수로 사용하려면... this 사용
// 멤버 함수로만 오버로딩해야 하는 연산자: =, [], (), ->
Cents operator + (const Cents &c2)
{
return Cents(this->getCents() + c2.getCents());
}
};
void add(const Cents &c1, const Cents &c2, Cents &c_out)
{
c_out.getCents() = c1.getCents() + c2.getCents();
}
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, sum);
cout << sum.getCents() << endl;
cout << add(cents1, cents2).getCents() << endl;
cout << (cents1 + cents2 + Cents(6) + Cents(10)).getCents() << endl;
//int i = 6, j = 8; cout << i + j << endl;
// 오버로딩이 불가능한 연산자!
// ?: :: sizeof . .*
// 오버로딩은 연산자 우선순위는 그대로임!! 오버로딩 시 주의하기
// 위험한 것보다는 불편한 게 낫다(ㅋㅋㅋ)
// ^(XOR): 우선순위가 매우 낮아서 괄호로 싸서 사용해야 함, 오버로딩 비추천
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; }
//void print()
//{
// cout << m_x << " " << m_y << " " << m_z;
//}
// 멤버 함수로는 불가능! 첫 번째 매개변수가 output stream
// 파일 출력이 가능!!
friend std::ostream& operator << (std::ostream &out, const Point &point)
{
out << "(" << point.m_x << " " << point.m_y << " " << point.m_z << ")";
return out; // 반환 타입이 ostream이기 때문에 체이닝(연쇄) 가능
}
friend std::istream& operator >> (std::istream &in, Point &point)
{
// 방어적 프로그래밍을 위한 조건 등... 필요
in >> point.m_x >> point.m_y >> point.m_z;
return in;
}
};
int main()
{
ofstream of("out.txt");
Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
Point p3, p4;
//p1.print();
//cout << " ";
//p2.print();
//cout << endl;
cout << p1 << " " << p2 << endl;
of << p1 << " " << p2 << endl;
cin >> p3 >> p4;
cout << p3 << " " << p4 << endl;
of.close();
return 0;
}
단항 연산자 오버로딩 하기
#include <iostream>
#include <fstream>
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
{
return Cents(-m_cents);
}
bool operator ! () const
{
// 무엇을 return할 것인지를 문맥에 따라 결정해야 함!
return (m_cents == 0) ? true : false;
}
friend std::ostream& operator << (std::ostream &out, const Cents ¢s)
{
out << cents.m_cents;
return out;
}
};
int main()
{
Cents cents1(6);
Cents cents2(0);
//int a = 3;
//cout << -a << endl;
//cout << !a << endl;
cout << cents1 << endl;
cout << -cents1 << endl;
cout << -Cents(-10) << endl;
auto temp = !cents1;
cout << !cents1 << " " << !cents2 << 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 bool operator == (const Cents &c1, const Cents &c2)
{
return c1.m_cents == c2.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 ¢s)
{
out << cents.m_cents;
return out;
}
};
int main()
{
//int a = 3, b = 3;
//if (a == b)
// cout << "Equal";
Cents cents1(6);
Cents cents2(6);
if (cents1 == cents2)
cout << "Equal" << endl;
cout << std::boolalpha;
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; }
// operator > 하면 error! less than이 구현되어야 함
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 ¢s)
{
out << cents.m_cents;
return out;
}
};
int main()
{
vector<Cents> arr(20);
for (unsigned i = 0; i < 20; i++)
arr[i].getCents() = i;
std::random_shuffle(begin(arr), end(arr));
for (auto &e : arr)
cout << e << " ";
cout << endl;
// Cents끼리 크기 비교 불가능해서 error! => overloading 필요
std::sort(begin(arr), end(arr));
for (auto &e : arr)
cout << e << " ";
cout << endl;
cout << std::boolalpha;
return 0;
}
증감 연산자 오버로딩 하기
#include <iostream>
using namespace std;
class Digit
{
private:
int m_digit;
public:
Digit(int digit = 0) : m_digit(digit) {}
//prefix
Digit & operator ++ ()
{
++m_digit;
// 자기 자신을 return한다고 보면 됨
return *this;
}
// postfix: parameter에 dummy가 필요함!!
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()
{
/*
int a = 10;
cout << ++a << endl; //11
cout << a << endl; //11
cout << a++ << endl; //11
cout << a << endl; //12
*/
Digit d(5);
cout << ++d << endl;
cout << d << endl;
cout << d++ << endl;
cout << d << endl;
return 0;
}
첨자 연산자 오버로딩 하기
#include <iostream>
#include <cassert>
using namespace std;
class IntList
{
private:
// 동적 할당 시, 반드시 해당 메모리가 allocate되어 있는지 주의!
// vector를 사용할 경우, vector 자체에 오버로딩이 잘되어 있기 때문에 그걸 사용할 수도 있음 => 템플릿
int m_list[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
public:
// parameter에 다른 데이터 타입도 들어올 수 있음, 용도에 따라 다름
// 값을 읽을 수도 있고, 접근할 수도 있게끔 return값은 reference, 항상 l-value여야 함
int & operator [] (const int index)
{
// assert로 미리 막아 줘야 runtime error debugging 시 편함
// subscript operator는 자주 사용하기 위해 만드는 것이기 때문에
// if문을 사용할 경우 속도가 느려짐, 퍼포먼스를 위해 if문은 권장X
assert(index >= 0);
assert(index < 10);
return m_list[index];
}
// const IntList의 경우, const로 선언한 함수 호출
// 값을 변경하지 않는 함수임을 명시(const)해 주어야 함
const int & operator [] (const int index) const
{
return m_list[index];
}
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] = 10;
//cout << my_list.getList()[3] << endl;
my_list[3] = 10;
cout << my_list[3] << endl;
const IntList my_clist;
//my_clist[3] = 10; // const라서 수정은 불가능함
cout << my_clist[3] << endl;
IntList *list = new IntList;
//list[3] = 10; // Not OK! 포인터 자체에 사용해 버림
(*list)[3] = 10; // OK! de-referencing
return 0;
}
괄호 연산자 오버로딩과 함수 객체
#include <iostream>
using namespace std;
//functor: 함수 객체
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;
}
형변환을 오버로딩 하기
#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; }
//integer형 형변환을 오버로딩
operator int()
{
cout << "cast here" << endl;
return m_cents;
}
};
class Dollar
{
private:
int m_dollars = 0;
public:
Dollar(const int& input) : m_dollars(input) {}
operator Cents()
{
return Cents(m_dollars * 100);
}
};
void printInt(const int &value)
{
cout << value << endl;
}
int main()
{
Cents c(10);
int value = (int)c;
value = int(c);
value = static_cast<int>(c);
printInt(c);
cout << endl;
Dollar dol(2);
Cents cen = dol;
printInt(cen);
return 0;
}
복사 생성자, 복사 초기화 반환값 최적화
#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);
}
// private로 옮겨 주면, 외부에서 사용할 수 없음! 복사 불가능!
// 보안성을 중요시 하는 경우 사용하기도 함
Fraction(const Fraction &fraction) // copy constructor
: m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
{
// 얼마나 자주 호출되는지 알아보기 위한 출력문
cout << "Copy constructor called" << endl;
}
friend std::ostream & operator << (std::ostream & out, const Fraction & f)
{
out << f.m_numerator << " / " << f.m_denominator << endl;
return out;
}
};
Fraction doSomething()
{
Fraction temp(1, 2);
cout << &temp << endl;
return temp;
}
int main()
{
Fraction frac(3, 5);
Fraction fr_copy(frac);
Fraction fr_copy2 = frac; // 이 경우에도 copy constructor 호출됨
cout << frac << fr_copy << fr_copy2 << endl;
// 이 경우에는 copy constructor 실행 X!! => 컴파일러가 생략함
Fraction fr_copy3(Fraction(3, 10));
// debug: copy constructor 실행
// release: copy constructor 실행 X, 반환값 최적화(컴파일러)
// debug 모드에서는 다른 주소(따라서 복사 진행), release 모드에서는 같은 주소 출력
Fraction result = doSomething();
cout << &result << endl;
return 0;
}
변환 생성자, explicit, delete
Converting constructor
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
// 강력하게 막아 버리기(함수 삭제)
Fraction(char) = delete;
explicit 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_numerator), m_denominator(fraction.m_denominator)
{
cout << "Copy constructor called" << endl;
}
friend std::ostream & operator << (std::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(frac);
doSomething(Fraction(7));
// 생성자 앞에 explicit라는 키워드가 있으면 불가능!!
//doSomething(7);
//Fraction frac2('c');
return 0;
}
대입 연산자 오버로딩, 깊은 복사, 얕은 복사
동적 할당된 메모리에 대한 포인터 변수를 멤버로 가지고 있는 class의 경우, 복사&대입의 경우 깊은 복사/얕은 복사 때문에 대입 연산자의 구현이 까다로워진다.
#include <iostream>
#include <cassert>
using namespace std;
class MyString
{
// 일반적으로 멤버 변수는 private으로 구현하나, 주소를 찍기 위하여 public 선언
//private:
public:
char *m_data = nullptr;
int m_length = 0;
public:
MyString(const char *source = "")
{
assert(source);
// null 문자를 추가하기 위하여 +1
m_length = std::strlen(source) + 1;
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;
}
char* getString() { return m_data; }
int getLength() { return m_length; }
};
int main()
{
MyString hello("Hello");
cout << (int *)hello.m_data << endl;
cout << hello.getString() << endl;
{
MyString copy = hello; // 복사 생성자 호출
//copy = hello; // 대입 연산자 호출
cout << (int *)copy.m_data << endl;
cout << copy.getString() << endl;
// 이 경우, 기본 복사 생성자에 의해 hello의 멤버변수들을 복사받는다.
// 포인터 또한 복사되기 때문에 hello.m_data와 copy.m_data가 가리키고 있는 주소가 동일해진다.
// scope를 벗어나면서 copy의 소멸자가 호출되고,
// 소멸자에서는 메모리 누수를 막기 위해 동적 할당된 m_data를 delete하여 heap에서 지운다.
}
// 따라서 같은 주소를 가리키고 있던 hello에서 접근하면 이상한 값이 출력된다.
cout << hello.getString() << endl;
return 0;
}
#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 = std::strlen(source) + 1;
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(const MyString &source)
{
cout << "Copy constructor" << endl;
m_length = source.m_length;
// source가 반드시 사라지는 경우라면 그대로 가져다 써도 OK
// 아니라면 메모리를 따로 잡아야 함!
// shallow copy: 주소값을 그대로 복사
// deep copy: 주소값이 아닌, 메모리를 재할당 후 값을 재복사
if (source.m_data != nullptr)
{
// 메모리를 새로 할당
m_data = new char[m_length];
// source의 데이터를 복사
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
}
// 구현하고 싶지 않을 때는 임시로 막아 버릴 수도 있음
//MyString(const MyString &source) = delete;
MyString& operator = (const MyString & source)
{
// shallow copy
//this->m_data = source.m_data;
//this->m_length = source.m_length;
cout << "Assignment operator " << endl;
// 대입 연산자일 경우, 자기가 자기에게 대입 가능 hello = hello;
// 프로그램에서는 문제가 생길 수 있기 때문에 주소가 같다면 그냥 return
if (this == &source) // prevent self-assignment
return *this;
// 이미 메모리를 가지고 있을 수 있기 때문에 먼저 지움
delete[] m_data;
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;
}
~MyString()
{
delete[] m_data;
}
char* getString() { return m_data; }
int getLength() { return m_length; }
};
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;
MyString str1 = hello; // Copy constructor
//MyString str1(hello); // 덜 헷갈리게 이렇게 선언 OK
MyString str2;
str2 = hello; // Assignment operator
// 이 방법이 싫다면...?
// 1. std::string을 사용
// 2. std::string을 상속받아서 추가 구현
// 3. member 변수에 std::string data 선언
return 0;
}
이니셜라이져 리스트 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];
}
// initializer_list를 parameter로 받으면 {} 초기화 가능
// 중복되는 기능은 쪼개서 한 곳에서만 실행이 되게끔 만들기!!
IntArray(const std::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]; // error!
// initializer_list는 [] operator를 지원하지 않음
// for문을 사용하려면 iterator 사용 => 추후 설명
}
~IntArray()
{
delete[] this->m_data;
}
// TODO: overload operator =
IntArray& operator = (const IntArray & source)
{
if (this == &source)
return *this;
delete[] m_data;
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new int[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
return *this;
}
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()
{
// 기본 자료형의 정적, 동적 배열 둘 다 initializer list로 초기화 가능
int my_arr1[5] = { 1, 2, 3, 4, 5 };
int *my_arr2 = new int[5]{ 1, 2, 3, 4, 5 };
// initializer list를 include 하면 가능!
auto il = { 10, 20, 30 };
//IntArray int_array { 1, 2, 3, 4, 5 };
IntArray int_array = { 1, 2, 3, 4, 5 }; // 가능
cout << int_array << endl;
return 0;
}
해당 포스트는 '홍정모의 따라하며 배우는 C++' 강의를 수강하며 개인 백업용으로 메모하였습니다.
인프런: https://www.inflearn.com/course/following-c-plus
홍정모의 따라하며 배우는 C++ - 인프런
만약 C++를 쉽게 배울 수 있다면 배우지 않을 이유가 있을까요? 성공한 프로그래머로써의 경력을 꿈꾸지만 지금은 당장 하루하루 마음이 초조할 뿐인 입문자 분들을 돕기 위해 친절하고 자세하게 설명해드리는 강의입니다. 초보로 시작하더라도 중급을 넘어 고급 프로그래머로 가는 길목에 들어서고 싶으시다면 최고의 디딤돌이 되어드리겠습니다. 여러분의 꿈을 응원합니다! 초급 프로그래밍 언어 C++ 온라인 강의 C++
www.inflearn.com
반응형