Ch 02. 연산자 오버로딩
산술 연산자 오버로딩
클래스끼리의 연산을 가능하게 연산자를 정의하는 것을 연산자 오버로딩이라고 한다.
#include <iostream>
using namespace std;
// operator overloading
class Vector
{
public:
float x;
float y;
float z;
}; // Class Vector
벡터 클래스를 이용한 연산자 오버로딩 예시
Vector v1{1, 2, 3};
Vector v2{4, 5, 6};
Vector v3 = v1 + v2; // 3행은 아래와 같다.
// Vector v3 = v1.operator+(v2);
operator+() 라는 이름의 함수를 오버로딩 한다고 생각하면 된다.
class Vector
{
public:
float x;
float y;
float z;
Vector operator+(const Vector& v) const //v1 + v2
{
Vector result;
result.x = x + v.x;
result.y = y + v.y;
result.z = z + v.z;
return result;
}
Vector operator-(const Vector& v) const //v1 - v2
{
Vector result;
result.x = x - v.x;
result.y = y - v.y;
result.z = z - v.z;
return result;
}
Vector operator-() const // -v1
{
Vector v;
v.x = -x;
v.y = -y;
v.z = -z;
return v;
}
void print() const
{
cout << x << ", " << y << ", " << z << endl;
}
};
int main()
{
Vector v1 = { 1, 2, 3 };
Vector v2 = { 4, 5, 6 };
Vector v3 = v1 + v2;
v3.print(); // 5, 7, 9
Vector v4 = -v3;
v4.print(); // -5, -7, -9
Vector v5 = v1 - v2;
v5.print(); // -3, -3, -3
return 0;
}
전위연산자 오버로딩과 후위연산자 오버로딩 구분하기
Vector& operator++() //전위 연산자 오버로딩
{
x++;
y++;
z++;
return *this;
}
Vector operator++(int) // 후위 연산자 오버로딩
{
Vector temp = *this;
x++;
y++;
z++;
return temp;
}
friend
위와 같이 클래스의 멤버함수로 포함시켜 오버로딩 하게 되면 vector * float 은 가능하지만 float * vector는 불가능하게 된다. 이를 해결하기 위해 전역함수로 오버로딩 할 수 있다.
class Vector
{
private: //private 필드
float x;
float y;
float z;
public:
Vector() //생성자
{
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
Vector(float x, float y, float z)
: x(x), y(y), z(z) {}
Vector operator+(const float& f)
{
Vector v;
v.x = x + f;
v.y = y + f;
v.z = z + f;
return v;
}
void print()
{
cout << x << ", " << y << ", " << z << endl;
}
};
int main()
{
Vector v1(1.0f, 2.0f, 3.0f);
Vector v2 = v1 + 3.0f;
v2.print();
//Vector v3 = 3.0f + v1; 오류가 발생한다.
return 0;
}
전역함수로 해결하면 이렇게 된다.
class Vector
{
private:
float x;
float y;
float z;
public:
Vector()
{
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
Vector(float x, float y, float z)
: x(x), y(y), z(z) {}
void print()
{
cout << x << ", " << y << ", " << z << endl;
}
friend Vector operator+(const Vector& v, float scalar);
};
Vector operator+(const Vector& v, float scalar)
{
Vector temp;
temp.x = v.x + scalar;
temp.y = v.y + scalar;
temp.z = v.z + scalar;
return temp;
}
Vector operator+(float scalar, const Vector& v)
{
return v+scalar;
}
int main()
{
Vector v1(1.0f, 2.0f, 3.0f);
Vector v2 = v1 + 3.0f;
v2.print();
Vector v3 = 3.0f + v1;
v3.print();
return 0;
}
전역함수로 오버로딩 하기 위해서는 클래스의 private 필드에 접근해야 한다. 클래스에서 특정 함수나 클래스에게 이를 허용해 주기 위해서 friend 키워드를 사용한다.
class Test
{
friend 함수오퍼레이터;
friend class 클래스이름;
//함수나 클래스에서 현재 클래스에 접근하는것을 허용한다.
}
비교 & 관계 연산자 오버로딩
#include <iostream>
#include <cstring>
#include <compare>
using namespace std;
class String
{
private:
char* _chars;
public:
explicit String(const char* chars)
// explicit 키워드를 붙이면 자동 변환이 일어나지 않는다.
: _chars(new char[strlen(chars) + 1])
{
strcpy(_chars, chars);
}
~String()
{
delete[] _chars;
}
bool operator==(const String& s) const
{
return strcmp(_chars, s._chars) == 0;
}
bool operator==(const char* s) const
{
return strcmp(_chars, s) == 0;
}
bool operator!=(const String& s) const
{
return !(*this == s);
}
bool operator<(const String& s) const
{
return strcmp(_chars, s._chars) < 0;
}
bool operator>(const String& s) const
{
return strcmp(_chars, s._chars) > 0;
}
bool operator <=(const String& s) const
{
return !(*this > s);
}
bool operator >=(const String& s) const
{
return !(*this < s);
}
strong_ordering operator<=>(const String& s) const
{
int result = strcmp(_chars, s._chars);
if (result < 0)
return strong_ordering::less;
if (result > 0)
return strong_ordering::greater;
return strong_ordering::equal;
}
void print()
{
cout << _chars << endl;
}
friend bool operator==(const char* s0, const String& s1)
// 멤버함수가 아님, 클래스 외부 함수가 인라인화 됨
{
return strcmp(s0, s1._chars);
}
};
int main()
{
String s0("a");
String s1("b");
if ((s0 <=> s1) < 0)
cout << "!!" << endl;
}
유의할 점 : explicit 키워드, friend 인라인화 정의
논리연산자 오버로딩
#include <iostream>
#include <cstring>
using namespace std;
class String
{
private:
char* _chars;
public:
String(const char* chars) : _chars(new char[strlen(chars) + 1])
{
strcpy(_chars, chars);
}
bool operator!() const
{
return strlen(_chars) == 0;
}
bool operator||(bool b) const
{
return strlen(_chars) > 0 || b;
}
};
bool func()
{
cout << "func" << endl;
return true;
}
// 논리 연산자 오버로딩은 주의해야한다
int main()
{
String s("");
if (!s) // 된다
{
}
//if (s) // 안 된다
//{
//}
String s0("abc");
if (s0 || func()) // s0가 true로 평가 됐지만 func가 실행 된다.
//(Short-circuit evaluation 안 됨)
{// s0, func()의 평가 순서 보장이 안 된다.
// (|| 를 오버로딩함으로서 시퀀스 포인트가 아니게 됨)
cout << "!!" << endl;
}
// && 오버로딩도 || 오버로딩과 같은 문제점을 가지고 있다
}
오버로딩하게 되면 short circuit evaluation이 안되는 것을 주의해야 한다.
비트 연산자 오버로딩
#include <iostream>
#include <string>
using namespace std;
class Vector
{
private:
int x;
int y;
int z;
public:
Vector()
{
}
Vector(int x, int y, int z)
:x(x), y(y), z(z)
{
}
friend ostream& operator<<(ostream& os, const Vector& v)
//벡터 출력 오버로딩
{
os << v.x << " " << v.y << " " << v.z;
return os;
}
friend istream& operator>>(istream& is, Vector& v)
//벡터 입력 오버로딩
{
string temp; //입력 형식 : 1 2 3
is >> temp;
v.x = stoi(temp);
is >> temp;
v.y = stoi(temp);
is >> temp;
v.z = stoi(temp);
return is;
}
Vector operator~() const
{
return Vector(~x, ~y, ~z);
}
Vector operator&(const Vector& v) const
{
return Vector(x & v.x, y & v.y, z & v.z);
}
Vector operator|(const Vector& v) const
{
return Vector(x | v.x, y | v.y, z | v.z);
}
Vector operator^(const Vector& v) const
{
return Vector(x ^ v.x, y ^ v.y, z & v.z);
}
Vector operator<<(int v) const
{
return Vector(x << v, y << v, z << v);
}
Vector operator>>(int v) const
{
return Vector(x >> v, y >> v, z >> v);
}
};
int main()
{
Vector v{ 1, 2, 3 };
cout << v << endl; // operator<<(cout, v);
operator<<(cout, v).operator<<(endl);
cin >> v; // operator>>(cin, v)
cout << v << endl;
Vector v0{ 0, 0, 0 };
cout << ~v0 << endl;
Vector v1{ 1, 2, 3 };
cout << (v0 & v1) << endl;
cout << (v0 | v1) << endl;
cout << (v1 ^ v1) << endl;
Vector v2{ 2, 4, 8 };
cout << (v2 << 2) << endl;
cout << (v2 >> 2) << endl;
}
첨자 연산자 오버로딩
벡터를 이용한 첨자 연산자 [ ] 오버로딩
class Vector
{
public:
float x, y, z;
Vector(float x, float y, float z)
: x(x), y(y), z(z){}
float& operator[](int index) //const 아닌 객체, 참조로 수정이 가능
{ //const 유무로도 오버로딩이 가능하다.
if (index < 1)
return x;
if (index < 2)
return y;
return z;
}
//const float& operator[](int index) const // 큰 객체 반환 시 const 참조
float operator[](int index) const
{
if (index < 1)
return x;
if (index < 2)
return y;
return z;
}
};
int main()
{
Vector(1, 2, 3);
cout << Vector[0] << Vector[1] << Vector[2] << endl;
// 1 2 3 출력
스트링의 첨자 연산자 오버로딩
class String
{
private:
char* _chars;
public:
String(const char* chars)
: _chars(new char[strlen(chars) + 1])
{
strcpy(_chars, chars);
}
~String()
{
delete[] _chars;
}
char operator[](int index) const
{
return _chars[index];
}
char& operator[](int index)
{
return _chars[index];
}
friend std::ostream& operator<<(std::ostream& os, const String& s)
{
os << s._chars;
return os;
}
};
const 객체와 비const비 const 객체가 둘 다 함수를 사용할 수 있게 const 함수와 비 const 함수를 모두 오버로딩 해준다.
간단한 해쉬 테이블을 이용한 첨자 연산자 오버로딩
#include <vector>
#include <string>
using Key = std::string;
using Value = std::string;
using KeyValue = std::pair<Key, Value>;
class Bucket
{
private:
std::vector<KeyValue> _items;
public:
Value& get(const Key& key)
{
for (KeyValue& keyValue : _items)
{
if (keyValue.first == key)
{
return keyValue.second;
}
}
_items.push_back(std::make_pair(key, Value()));
return _items.back().second;
}
};
class HashTable
{
private:
std::vector<Bucket> _buckets;
int hash(const Key& key) const
{
int h = 0;
for (char ch : key)
{
h += ch;
}
return h;
}
public:
HashTable(int size = 100)
: _buckets(std::vector<Bucket>(size))
{}
Value& operator[](const Key& key)
{
int bucketIndex = hash(key) % _buckets.size();
Bucket& bucket = _buckets[bucketIndex];
return bucket.get(key);
}
};
// bucket, bucket, bucket, bucket, bucket, bucket...
// key: "abc" <- hash
// value: "def"
// key: "cba" <- hash
// value:: "123"
대입 연산자 오버로딩, 복사 생성자
int a = 10;
int b = a; //복사
int func(int num){ //복사
return num; //복사
}
변수의 대입이나, 함수의 호출, 함수의 반환시에 복사가 일어난다. 이때 사용되는 것이 복사 생성자이다.
Person(const Person& person) //복사생성자의 프로토타입
우리가 복사생성자를 따로 정의하지 않아도 컴파일러에서 디폴트 복사생성자를 삽입한다. 디폴트 복사생성자는 얕은 복사(단순히 데이터만 복사) 시에는 문제가 없지만 깊은 복사(주소값의 참조값을 복사해야 하는 경우)에는 문제가 생긴다. 때문에 직접 복사생성자를 정의해주어야 한다.
class GoodPerson
{
private:
float _weight;
float _height;
char* _name;
public:
GoodPerson() {}
GoodPerson(float weight, float height, const char* name) //생성자
: _weight(weight), _height(height), _name(new char[strlen(name) + 1])
{
strcpy(_name, name);
}
GoodPerson(const GoodPerson& person) //복사 생성자
: GoodPerson(person._weight, person._height, person._name) //위임 생성자
{
}
~GoodPerson() //파괴자
{
delete[] _name;
}
GoodPerson& operator=(const GoodPerson& person) //대입연산자 오버로딩
{
_weight = person._weight;
_height = person._height;
delete[] _name;
_name = new char[strlen(person._name) + 1];
strcpy(_name, person._name);
return *this;
}
void print() const
{
using namespace std;
cout << _name << endl;
cout << _weight << endl;
cout << _height << endl;
}
};
레퍼런스나 임시객체로 전달되는것 같은 몇 가지 경우에는 복사가 일어나지 않는다.
변환 연산자 오버로딩, 변환 생성자
explicit 키워드를 함수 앞에 붙이게 되면 암시적인 형 변환이 금지된다.
class String
{
private:
char* _chars;
public:
explicit String(const char* chars) // 생성자
: _chars(new char[strlen(chars) + 1])
{
strcpy(_chars, chars);
}
};
explicit이 붙으면 암시적 형변환이 불가능해서 다음과 같은 표현이 불가능하다.
String s = "abc"; // 오류
String s("abc"); // 가능
복사 생성자에도 explicit을 달 수 있다.
explicit String(const String& s) // 복사 생성자
: _chars(new char[strlen(s._chars) + 1])
{
strcpy(_chars, s._chars);
}
복사 생성자에 explicit을 붙이게 되면 복사 생성자의 암시적 호출이 금지되어 다음과 같은 표현이 불가능하다.
String s1;
String s2 = s1; // 오류
String s2(s1); // 가능
String s2 = String(s1); // 오류
func(s1); // 오류 : 파라미터로 전달할 때 복사생성자가 암시적 호출 됨
다음과 같이 func를 정의한다면 사용할 수 있음
void func(const String& s) // 참조로 전달하여 복사가 일어나지 않음
//즉 복사생성자에 explicit을 붙여 커다란 데이터의 복사를 막을 수 있음
C++11부터는 중괄호를 이용하여 초기화를 할 수 있다.
int a{10};
//생성자도 중괄호로 호출할 수 있다.
String s{"Hello"};
두 개의 파라미터를 받는 변환 생성자
String(const char* s0, const char* s1)
: _chars(new char[strlen(s0) + strlen(s1) + 1])
{
_chars[0] = '\\0';
strcat(_chars, s0);
strcat(_chars, s1);
}
void main()
{
String s0{"Hello", "World"}; // 가능
String s0 = {"Hello", "World"}; // 가능, explicit이 있다면 불가능
}
std::initializer_list를 사용하면 여러 개의 파라미터를 인자로 받는 생성자를 정의할 수 있다.
String(std::initializer_list<const char*> strs)
: _chars(nullptr)
{
size_t size = 0; // size_t는 부호없는 정수형타입
for (const char* str : strs)
{
size += strlen(str);
}
_chars = new char[size + 1];
_chars[0] = '\\0';
for (const char* str : strs)
{
strcat(_chars, str);
}
}
변환연산자 오버로딩
//변환 연산자 오버로딩 문법
class Example{
public:
operator 반환형() const{
//로직
}
};
// 논리 연산자 오버로딩 대신 bool 변환 연산자를 고려해보자
// explicit 가 없다면 암시적으로도 형변환이 가능해진다
operator bool() const
{
return strlen(_chars) > 0;
}
// explicit이 있다면 다음과 같이 명시적 변환을 해주어야 한다.
bool b = (bool)s0;
bool b = bool(s0);
호출 연산자 오버로딩, 함수 객체
함수를 호출할 때 사용하는 괄호를 호출 연산자라고 한다.
class Max
{
public:
int operator()(int x, int y)
{
return max(x, y);
}
};
void main()
{
Max mx;
mx(5,10); //10
}
algorithm 헤더의 max 함수와 차이가 없어 보이지만 class를 사용하기 때문에 값을 저장할 수 있다는 장점이 있다.
class AccMax
{
private:
// 일반 함수와는 다르게 상태를 저장할 수 있다
int _max = numeric_limits<int>::min();
public:
int operator()(int x)
{
return _max = max(x, _max); //역대 최댓값 리턴
}
};
이런 함수를 함수객체또는 Functor라고 부른다.
std::for_each(begin, end, 함수); 예시
struct Print
{
public:
void operator()(int v) const
{
cout << v << endl;
}
};
int main()
{
Print print;
int nums[] = { 1, 2, 3, 4, 5 };
std::for_each(nums, nums + size(nums), print);
function<void(int)> func = print; //functional 예시
func(1);
plus<int> p; // 함수 객체, 프리디파인 함수
cout << p(1, 2) << endl;
negate<int> n; // 함수 객체
cout << n(1) << endl;
equal_to<int> et; // 함수 객체
cout << et(1, 1) << endl;
}
사용자 정의 리터럴
리터럴 형식 표현
10
10.0f
10u
이러한 접미사 형들을 정의해 줄 수 있다.
chrono 헤더 예시
#include <chrono>
chrono::minutes m = 24h + 5min;
cout << m.count() << endl; // 1445 (분)
길이 접미사 오버로딩
//문법
클래스명 operator"" 접미사명(인자);
class Length
{
private:
const long double _value;
Length(long double value) : _value(value)
{
}
friend Length operator"" _m(unsigned long long value); //정수형
friend Length operator"" _m(long double value); // 실수형
friend Length operator"" _km(unsigned long long value);
friend Length operator"" _km(long double value);
friend Length operator"" _mm(unsigned long long value);
friend Length operator"" _mm(long double value);
friend Length operator"" _cm(unsigned long long value);
friend Length operator"" _cm(long double value);
public:
long double m() const { return _value; }
long double km() const { return _value * 0.001; }
long double mm() const { return _value * 1000; }
long double cm() const { return _value * 100; }
Length operator+() const
{
return Length(_value);
}
Length operator-() const
{
return Length(-_value);
}
Length operator+(const Length& length) const
{
return Length(_value + length._value);
}
Length operator-(const Length& length) const
{
return Length(_value - length._value);
}
};
Length operator"" _m(unsigned long long value)
{
return Length(value);
}
Length operator"" _m(long double value)
{
return Length(value);
}
Length operator"" _km(unsigned long long value)
{
return Length(value * 1000L);
}
Length operator"" _km(long double value)
{
return Length(value * 1000L);
}
Length operator"" _mm(unsigned long long value)
{
return Length(value * 0.001L);
}
Length operator"" _mm(long double value)
{
return Length(value * 0.001L);
}
Length operator"" _cm(unsigned long long value)
{
return Length(value * 0.01L);
}
Length operator"" _cm(long double value)
{
return Length(value * 0.01L);
}
'C & C++ > C++' 카테고리의 다른 글
[C++] 형변환 (0) | 2024.08.07 |
---|---|
[C++] 상속 (0) | 2024.08.05 |
[C++] 클래스 (0) | 2024.07.13 |
[C++] 함수, 범위, 공간 (0) | 2024.07.08 |
[C++] 포인터, 참조 (1) | 2024.07.01 |