KoreanFoodie's Study

C++ 기초 개념 5-2 : 입출력, 첨자, 타입변환, 증감 연산자 오버로딩 본문

Tutorials/C++ : Beginner

C++ 기초 개념 5-2 : 입출력, 첨자, 타입변환, 증감 연산자 오버로딩

GoldGiver 2021. 12. 22. 17:17

모두의 코드를 참고하여 핵심 내용을 간추리고 있습니다. 자세한 내용은 모두의 코드의 씹어먹는 C++ 강좌를 참고해 주세요!

friend 키워드

클래스 내부에서 다른 클래스나 함수들을 friend로 정의할 수 있는데, friend로 정의된 클래스나 함수들은 원래 클래스의 private로 정의된 변수나 함수들에 접근할 수 있다.

class A {
private:
    void DoSth() {};
    int a;
    
    friend class B;
    friend void func();
}

class B {
public:
    void b() { A a; a.DoSth(); a.a = 1; }
}

void func() {
    A aa;
    aa.a= 2;
    a.DoSth();
}

위에서 friend로 지정된 클래스 B와 함수 func( )는 클래스 A의 private한 멤버와 함수에 접근할 수 있다.

반면, friend는 짝사랑 같은 관계이므로, 클래스 A에서 클래스 B의 private한 멤버 변수와 함수에는 접근할 수 없다!

 

 

입출력 연산자 오버로딩

우리가 생성한 클래스를 기본 타입처럼 입출력 함수의 인자로 주고 싶다면, ostream에서의 연산자를 오버로딩하면 된다.

std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << " ( " << c.real << " , " << c.img << " ) ";
    return os;
}

하지만 만약 Complex 클래스의 real과 img 변수가 private일 경우, 위의 경우처럼 바로 사용할 수가 없다. 대신 Complex 클래스 내부에서 std::ostream& operator<< 함수를 friend로 지정해주면 된다!

friend std::ostream& operator<<(std::ostream& os, const Complex& c);

 

 

첨자 연산자 (operator[ ])

배열에서 원소를 지정할때 우리는 [ ] 연산자를 사용한다. 기존에 만들었던 MyString에서 [ ] 를 구현해보자.

char& MyString::operator[](const int index) {
    return str.string_content[index];
}

 

 

int Wrapper 클래스 - 타입 변환 연산자

기본 클래스인 int형을 감싸는 wrapper 클래스를 구현한다고 해 보자.

class Int {
private:
    int i_;
    
public:
    Int(int i) : i_(i) {}
    Int(const Int& i) : i_(i.i_) {}
};

이때, 복사 생성자에서 i_(i.i_) 가 어떻게 가능한지 의문을 가질 수 있다. 왜냐하면 i_는 private으로 지정되어 있기 때문이다.

그런데 private 접근 지정자는 객체 레벨이 아닌 클래스 레벨에서 적용된다. 즉, 클래스가 같다면, 클래스 내부의 다른 인스턴스의 private 멤버와 함수에 접근이 가능하다. 

 

그런데 우리가 정의한 Int Wrapper 클래스와 int 사이에 덧셈을 해야 한다면 어떨까? 그런 경우를 위해서 연산자를 오버로딩하는 것도 생각해볼 수 있지만, 그것보다 더 좋은 방식은 Int -> int 로의 암시적 형 변환을 가능케 해주는 것이다. 타입 변환 연산자는 다음과 같이 간단하게 구현이 가능하다.

Int::operator int() { return i_; }

주의할 점은, 생성자처럼 함수의 리턴 타입을 쓰면 안된다는 것이다.

따라서 다음과 같은 연산이 정상적으로 작동한다.

Int x = 1;
int y = 2;
std::cout << x + y << std::endl; // 3

 

 

전위/후위 증감 연산자

++, -- 전위/후위 증감 연산자를 구현해 보자.

연산자 오버로딩시, a++ 와 ++a를 구분하여 오버로딩해 주어야 하는데, 형식은 다음과 같다.

// 전위 증감 연산자
operator++();
operator--();

// 후위 증감 연산자
operator++(int x);
operator--(int x);

인자 x는 아무런 의미가 없고, 단순히 컴파일러 상에서 전위와 후위를 구별하기 위해 int 인자를 넣어주는 것이다. 따라서 operator++(int) 처럼 써도 무방하다.

중요한 점은, 전위 증감 연산의 경우 값이 바뀐 자신을 리턴하며, 후위 증감의 경우 값이 바뀌기 이전의 객체를 리턴한다는 것이다.

 

후위 증감의 경우 추가적으로 복사 생성자를 호출하기 때문에 전위 증감 연산보다 더 느리다!

위의 Int wrapper 클래스를 활용한 구현 코드를 보자.

// 전위
Int& Int::operator++() {
    ++i_;
    return *this;
}

// 후위
Int Int::operator++(int) {
    Int ret(*this);
    ++i_;
    return ret;
}

 

오버로딩에 관한 자세한 내용은 이 글을 참고하자.

 
Comments