2025. 5. 2. 12:33ㆍProgramming Language/C++
우선 비트 연산자의 종류로는 ~(NOT)연산자, &(AND)연산자, |(OR)연산자, ^(XOR)연산자, <<(Left Shift)연산자, >>(Right Shift)연산자가 있다.
각각의 연산자에 대한 오버로딩에 대해서 한번 알아보도록 하자.
1. << 연산자
<< 연산자와 >> 연산자는 각각 std 에서 cout, cin과 같은 입출력 연산자로 사용했었는데 이 입출력 연산자로써 오버로딩을 한번 해보도록 하자.
먼저 우리가 이전에 만들었던 Vector 클래스를 cout으로 넘겨주는 연산을 한번 확인해보자.
먼저 Vector 클래스를 만들어주고
우리가 하고 싶은건
이렇게 출력할 수 있도록 구현해보고자 하는것이다.
그러면 사실 cout이 좌항으로 cout.operator(v)가 되어야하는데 사실 cout은 표준 입출력이기 때문에 우리가 구현을 다시할 수 가 없기에 기존에 전역 함수로 만들었던 operator(cout, v)와 같은 구현을 해보도록 하자.
먼저 전역함수를 하나 만들어보는데 출력만 할것이니까 반환형은 void로 하고 함수를 만들어준다.
이때 cout의 타입을 한번 확인해보면
std의 ostream타입임을 알 수 있다.
이렇게 매개변수를 넣어주자.
그리고 내부에는 그냥 os 를 사용해서 공백을 구분으로 해서 멤버 변수를 출력하는 문을 만들어주고
friend를 사용해서 프로토 타입을 class 내부에 넣어주자.
이렇게 하면 끝난 건데 호출 부를 다시보면
이렇게 에러가 나는 것을 볼 수 있다.
보면 사실 저 연산은
std::cout << v << std::endl;
↓
operator<<(std::cout, v) << std::endl;
이것인데 operator<<는 반환형이 없는 void형태이기에 다음 << 연산을 처리할 수 없게 되는 것이다.
그래서 ostream을 다시 반환해줘야만 한다.
이제 실행시켜보면
이렇게 결과가 잘 나오는 것을 볼 수 있다.
보면
이 둘은 동일하게 같은 연산이라는 것을 알 수 있다.
2. >> 연산자
이제 입력 연산자로써 >> 을 오버로딩 해보자.
먼저 cin이 무슨 타입인지 확인해보고
istream타입이니 반환형은 void에 매개변수 첫 번째로는 istream객체를 참조로 받아주고 두번째 매개변수로는 Vector의 값을 변경해야 함으로 그냥 Vector객체를 참조값으로 받아주자.
그리고 내부에서 istream객체를 사용해서 Vector의 각 멤버 변수의 값을 입력해주고
마지막에 이 값을 cout을 통해서 출력해주자.
실행해보면
이렇게 입력을 잘 받는것을 볼 수있다.
만약 이 부분을 String으로 받은 후에 stoi로 하여 멤버 변수에 넣어주는 방법으로 구현도 가능하다.
물론 이때는 string을 include 해줘야 한다.
사실 강사는 이 방법으로 풀었는데 궂이 이렇게 구현해야하는가 싶은 의문이 있다.
타입에 대한 안정성을 위해서인가.. 싶기도 하다 내가 한건 뭔가 암시적 형변환을 일으켜 성능에 큰 차이를 줄지도 모른다는 생각이 들기도 한다..
알아보니 두번째 방법은 try-catch를 사용해서 이런 저런 입력 검증, 안정성등 확장성이 있는 상태로 확인됨.
왠만 하면 정석적으로 구현하는게 좋을 것 같기도 함...
저런 부분에서는 뭔가 C++하는 개발자로써의 머리는 아닌듯 싶음 난....
그 외 연산자의 경우는 사실 보면 간단하게 사용할것 같아서 그냥 코드만 짰다 그냥 보고 이해하셔도 이해할 정도로 쉬울것으로 보인다.
#include <iostream>
#include <string>
class Vector {
private:
int x, y;
public:
Vector(int x, int y)
:x(x), y(y)
{
}
friend std::ostream& operator<<(std::ostream& os, const Vector& v) {
os << "Vector x : " << v.x << ", Vector y : " << v.y;
return os;
}
friend std::istream& operator>>(std::istream& is, Vector& v) {
std::string temp;
is >> temp;
v.x = stoi(temp);
is >> temp;
v.y = stoi(temp);
return is;
}
Vector operator~() const {
return Vector{ ~x, ~y };
}
Vector operator&(const Vector& v) const {
return Vector{ x & v.x, y & v.y };
}
Vector operator|(const Vector& v) const {
return Vector{ x | v.x, y | v.y };
}
Vector operator^(const Vector& v) const {
return Vector{ x ^ v.x, y ^ v.y };
}
Vector operator<<(const int num) const {
return Vector{ x << num, y << num };
}
Vector operator>>(const int num) const {
return Vector{ x >> num, y >> num };
}
void print() {
std::cout << "x: " << x << ", y: " << y << std::endl;
}
};
int main() {
Vector v{ 2, 2 }; // 0010
Vector v0 = ~v;
v0.print();
Vector v1{ 6, 6 }; // 0110
Vector v2 = v & v1;// 0010
v2.print();
Vector v3 = v | v1;
v3.print(); // 0110
Vector v4 = v ^ v1;
v4.print(); // 0100
Vector v5 = v << 1;
v5.print(); // 0010 << 1 = 0100
Vector v6 = v >> 1;
v6.print(); // 0010 >> 1 = 0001
}
여기서 주의해야할 점은 나는 매개변수의 타입을 const Vector& 로 넣긴 했다만 매개변수의 타입을 Vector&로만 할 경우 문제가 발생할 수 있는데 아래를 보면 우리가 만들었던 <<출력 연산자에서 문제가 생겼다.
출력 연산자를 보면
매개변수의 형태중 Vector가 그냥 Vector& 참조 타입인데 ~연산자의 반환타입에서 보면
이 형태임을 알 수 있고 이를 종합적으로
이 연산에서 확인해보면
이와 동일한 상태인건데 위 처럼 Vector{2, 2} 처럼 우항에서 단기적으로 생성되는 이름없는 값, 일회성 값에 대해서는 참조가 불가능하다.
이는 우항의 값이 저 line을 지나가면 사라지는 값이기에 참조가 불가능하고 컴파일러가 알리는 것이다.
이를 해결하기 위해서는 Vector의 참조 타입을 const로 변경해주면 컴파일러가 우항의 Vector의 값을 유지할 수 있도록 보장해준다.
그렇기에 여기서는
이러한 형태가 되어야 하고 좌항에 해당하는
이 매개변수의 타입이
이렇게 const 타입이여만 한다는 점을 유의하자.
'Programming Language > C++' 카테고리의 다른 글
Part2::Ch 02. 연산자 오버로딩- 07. 대입 연산자 오버로딩, 복사 생성자 (0) | 2025.05.03 |
---|---|
Part2::Ch 02. 연산자 오버로딩- 06. 첨자 연산자 오버로딩 (0) | 2025.05.02 |
Part2::Ch 02. 연산자 오버로딩- 04. 논리 연산자 오버로딩 (0) | 2025.05.02 |
Part2::Ch 02. 연산자 오버로딩- 03. 비교&관계 연산자 오버로딩 (0) | 2025.05.02 |
Part2::Ch 02. 연산자 오버로딩- 01&02. 산술 연산자 오버로딩 (0) | 2025.05.02 |