모두의코드 : 씹어먹는 C++ - <5 - 2. 입출력, 첨자, 타입변환, 증감 연산자 오버로딩>

YP J·2022년 6월 17일
0

모두의코드 C++

목록 보기
4/11

이번 강좌에서는

  • 멤버 함수가 아닌 연산자 함수 오버로딩

  • 입출력 연산자 오버로딩 (정확히 보면 <<, >> 연산자)

  • 첨자 연산자 [] 오버로딩

  • 타입 변환 연산자 오버로딩

  • 증감 연산자 ++, -- 오버로딩

friend 키워드

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

class A
{
	 private:
		void private_func() {}
		int private_num;

		// B 는 A 의 친구!
		friend class B;

		// func 은 A 의 친구!
		friend void func();
};

class B
{
	public:
		void b()
		{
			A a;

			// 비록 private 함수의 필드들이지만 친구이기
			// 때문에 접근 가능
			a.private_func();
			a.private_num = 2;
		}
};

void func()
{
  A a;

  // 마찬가지로 프라이빗이지만 접근 가능.
  a.private_func();
  a.private_num = 2;
}

int main() {}

// B 는 A 의 친구!
friend class B;

// func 은 A 의 친구!
friend void func();

위 처럼 클래스 B와 ,void함수 func 을 A 의 친구라 선언했다.
이렇게 친구라 서언 하면
B와 func 안에서 A 의 모든 private 멤버 함수들과 멤버 변수들에 대한 접근 권한을 부여하게 된다.

그러나
B는 A의 모든 private들을 볼 수있지만, B 안에서 A를 friend 로 지정 하지 않는 이상 , A 는 B의 private개체들에 접근 불가능

예를들어

class A
{
	private:
    	int x;
      	
        friend class B;
};

class B
{
	private:
    	int y;
};

B에는 A가 friend 라고 지정 하지 않았으므로,
A 는 B의 private 변수인 int y 에 접근 불가능.

이항 연산자

a = a + "-1.1";
은 되지만
a = "-1.1" + a
Q.는 안 됨 그 이유는??

A.임의의 연산자 @ 라 칭함. (실제론 없음 C++에서)

*a.operator@(b);  
*operator@(a,b);  

컴파일러는 둘 중 가능한 녀석을 택해서 처리한다.
a.operator@(b) 에서의 operator@ 는 a의 클래스의 멤버함수로써 사용되는것이고,

operator@(a,b) 에서의 operator@ 는 클래스 외부에서 정의되어 있는 일반적인 함수를 의미하게 된다.
따라서 이경우를 처리하기 위해 함수를 정의해봅시다.

참고로 이는 일부 연산자들에 대해서는 해당되지 않는데 대표적으로 [] 연산자 (첨자), -> 연산자 (멤버 접근), 대입 연산자 (=), () 함수 호출 연산자들의 경우 멤버 함수로만 존재할 수 있습니다. 즉, 따로 멤버 함수가 아닌 전역 함수로 뺄 수 없다는 의미 입니다. 따라서 이들 함수를 오버로딩 할 때 주의하시기 바랍니다.

해결 방법은 일단 스킵

입출력 연산자 오버로딩 하기

std::cout << a;

이건 사실 std::cout.operator<<(a) 를 하는것.
즉 어떤 std::cout 이라는 객체에 멤버함수 operator<< 각 정의 되어 있어서 a 를 호출하게 되는것.

그런데 std::cout 이 int나 double 변수 심지어 문자열 까지 자유자재로 출력 가능한 이유는
그 경우모두 오버라이딩 되어 있기 때문이다 .operator<<

실제로 우리가 include 하던 iostream의 헤더 파일을 살펴보면 (실제로는 ostream에있음, iostream이 ostream을 include 하고있음)

ostream& operator<<(bool val);
ostream& operator<<(short val);
ostream& operator<<(unsigned short val);
ostream& operator<<(int val);
ostream& operator<<(unsigned int val);
ostream& operator<<(long val);
ostream& operator<<(unsigned long val);
ostream& operator<<(float val);
ostream& operator<<(double val);
ostream& operator<<(long double val);
ostream& operator<<(void* val);

그렇다면 우리의 Complex 클래스에서 ostream 클래스의 연산자 operator<< 를 자유롭게 사용할수 있으면 어떨까??

ex)

Complex c;
std::cout << c;

은 마치

Complex c;
c.println();

ostream 클래스에 operator<< 멤버 함수를 새롭게 추가하는것은 불가능 합니다.
이를 위해선 표준 헤더파일의 내용을 수정해야 하기 때문이다.

따라서 우리는 ostream클래스에 Complex 객체를 오버로딩 하는 operator<< 연산자 함수를 추가할 수는 없다.

하지만 우리는 클래스의 연산자 함수를 추가하는 방법으로 ,
멤버함수를 사용하는 것 말고도 한 가지더 있다.

바로 ostream클래스 객체와 Complex 객체 두개를 인자로 받는 전역 operator<< 함수를 정의하면 됩니다.

std::ostream& operayot<<(std::ostream& os, const Complex& c)
{
	os << "( " << c._real << " , " << c._img << " )";
    return os;
}

참고로 operator<<에서 ostream& 타입을 리턴하는 이뉴는 다음과 같은 문장을 처리할수 있기 위함이다.

std::cout << "a value is : " << a << ". " << std::endl;

하지만 위 operator<< 의 경우 한가지 문제가 있는데
operator<< 에선 c._real 과 c._img 에 접근할수 없다
왜냐하면 _real, _img 모두 Complex 클래스의 private 멤버 변수 이기 때문.

따라서 이를 해결하기 위해 세가지 방법을 고려할수있다.

  1. 그냥 _real 과 _img 를 public으로 바꾼다.

  2. Complex 에 print(std::ostream& os) 와 같은 멤버함수를 추가한 뒤, 이를 operator<< 에서 호출한다.

  3. 위 operator<< 를 friend로 지정한다.

3번째로 해보자.

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

위와 같이 friend 선언을 해주면 된다.

물론 무분별하게 friend 키워드 남발하는것 권장 하지 않는다.

비슷한 방법으로 Complex 객체 c 에 대해 cin >> c; 와 같은 작업을 할 수 있다.

다만 cin 은 istream 객체이고, operator>> 를 오버로딩 해야된다.

#include <iostream>
#include <cstring>

class Complex {
 private:
  double real, img;

  double get_number(const char* str, int from, int to) const;

 public:
  Complex(double real, double img) : real(real), img(img) {}
  Complex(const Complex& c) { real = c.real, img = c.img; }
  Complex(const char* str);

  Complex& operator+=(const Complex& c);
  Complex& operator=(const Complex& c);

  // 나머지 연산자 함수들은 생략 :)

  friend std::ostream& operator<<(std::ostream& os, const Complex& c);
  friend Complex operator+(const Complex& a, const Complex& b);
};

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

+ ...

첨자 연산자(operator[])

pass

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

Wrapper 클래스는 무언가를 포장하는 클래스.
C++에서 어떤 경우에 기본 자료형 들을 객체로써 다루어야 할 때가 있습니다.

이럴때 기본 자료형들 (int, float 등등) 을 클래스로 포장해서 각각의 자료형을 객체로 사용하는것을 Wrapper클래스를 이용한다는뜻.

즉 int 자료형을 감싸는 int Wrapper 클래스 Int 는 다음과 같이 구상할수있다.

~pass

전위,후위 증감 연산자

++,--
++a;, a++;

둘다 operator++ 이기 때문에

operaotr++();
operator--();

은 전위 증감 연산자 (++x, --x)를 오버로딩 하게된다.

operator++(int x);
operator--(int x);

는 후위 증감 연산자.
물론 인자 x는 아무런 의미가 없다.
단순히 컴파일러 상에서 전위와 후위를 구분하기 위해 int 인자를 넣은것.

실제로 인자로 들어가는 값을 사용할 경우는 없다.
따라서 그냥

operator++(int);
operator--(int);

로 해도 무방.

  • 전위 증감 연산자의 경우 값이 바뀐 자기 자신을 리턴해야하고
  • 후위 증감 연산자의 경우 바뀌기 이전의 객체를 리턴해야 한다.

왜냐면

int x =1;
func(++x);
>> func 에는 2가 인자로 전달됨.

int x =1;
func(x++);
>> func에1이 인자로 전달 되고 나중에 x++ 되어서 2가됨,
  • 전위
A& operator++()
{
	//A++ 을 수행한다.
    return *this;
}

++ 연산 수행후에 자기자신을 반환.

  • 후위
A operator++(int)
{
	A temp(A);
    // A++ 수행
    return temp;
}

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

연산자 오버로딩 정리

  • 두개의 동등한 객체 사이에서의 이항 연산자는 멤버 함수가 아닌 외부 함수로 오버로딩 하는것이 좋다
    (예를들어 Complex 의 operator+(const Complex&, const Complex& const) 와 같이 말이다.)

  • 두 개의 객체 사이의 이항 연산자 이지만 한 객체만 값이 바뀐다던지
    동등하지 않는 이항 연산자는 멤버 함수로 오버로딩 하는것이 좋습니다.
    ( ex. operator+= 는 이항 연산자 이지만 operator+=(const Complex&)가 낫다)

  • 단항 연산자는 멤버 함수로 오버로딩 하는것이 좋다.
    ( ex. operator++ 의 경우 멤버 함수로 오버로딩 합니다)

  • 일부 연산자들은 반드시 멤버 함수로만 오버로딩 해야한다.

profile
be pro

0개의 댓글