728x90
728x90

10-1 연산자 오버로딩의 이해와 유형

함수가 오버로딩되면, 이름은 하나지만 오버로딩 된 수만큼 다양한 기능을 제공한다.
연산자 오버로딩도 기존에 존재하던 연산자의 기본 기능 외 다른 기능을 추가할 수 있다.

 

operator+ 함수
// FirstOperationOverloading.cpp 

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x = 0, int y=0): xpos(x), ypos(y)
    {   }
    void ShowPosition() const // 내부출력문 const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    } 
    Point operator+(const Point& ref) // 연산자 오버로딩
    {
        Point pos(xpos + ref.xpos, ypos + ref.ypos); 
        return pos;    //두 객체의 멤버별 덧셈결과로 복사생성자가 호출되며 새로운 Point 객체가 만들어지고 반환되어 pos3를 초기화 한다
    }
};

int main()
{
    Point pos1(3, 4);
    Point pos2(10, 20);
    Point pos3 = pos1.operator+(pos2);    //pos1객체의 멤버함수를 호출하면서 인자로 pos2를 전달
    
    // Point pos3 = pos1 + pos2; //컴파일 가능, 같은결과
    // pos1 + pos2는 pos1.operator+(pos2)의 다른 표현
    // 변환되기 위해서 약속된 변환의 규칙이 있다

    pos1.ShowPosition();
    pos2.ShowPosition();
    pos3.ShowPosition();

    return 0;
}

 

 

main함수를 변경하여 다시 실행해본다.

 

// OverloadingOperation.cpp  

#include <iostream>
using namespace std; 

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x = 0, int y = 0) : xpos(x), ypos(y)
    {   }
    void ShowPosition() const // 내부출력문 const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
    Point operator+(const Point& ref) // 연산자 오버로딩
    {
        Point pos(xpos + ref.xpos, ypos + ref.ypos);
        return pos;    //두 객체의 멤버별 덧셈결과로 복사생성자가 호출되며 새로운 Point 객체가 만들어지고 반환되어 pos3를 초기화 한다
    }
};

int main()
{
    Point pos1(3, 4);
    Point pos2(10, 20);
    // Point pos3 = pos1.operator+(pos2);    //pos1객체의 멤버함수를 호출하면서 인자로 pos2를 전달

    Point pos3 = pos1 + pos2; //컴파일 가능, 같은결과
    // pos1 + pos2는 pos1.operator+(pos2)의 다른 표현
    // 변환되기 위해서 약속된 변환의 규칙이 있다

    pos1.ShowPosition();
    pos2.ShowPosition();
    pos3.ShowPosition();

    return 0;
}

 

 

Point pos3 = pos1.operator+(pos2);    //pos1객체의 멤버함수를 호출하면서 인자로 pos2를 전달
Point pos3 = pos1 + pos2; //컴파일 가능, 같은결과로 위의 다른 표현
...변환되기 위해서 약속된 변환의 규칙이 있다.

 

 

복사생성자 (이동)

더보기

C++에서 복사 생성자란 자신과 같은 클래스 타입의 다른 객체에 대한 참조(reference)를 인수로 전달받아, 그 참조를 가지고 자신을 초기화하는 방법입니다. 복사 생성자는 새롭게 생성되는 객체가 원본 객체와 같으면서도, 완전한 독립성을 가지게 해줍니다. 왜냐하면, 복사 생성자를 이용한 대입은 깊은 복사(deep copy)를 통한 값의 복사이기 때문입니다.

Book(const Book&);

 

복사 생성자는 다음과 같은 상황에서 주로 사용됩니다. 

1. 객체가 함수에 인수로 전달될 때
2. 함수가 객체를 반환값으로 반환할 때
3. 새로운 객체를 같은 클래스 타입의 기존 객체와 똑같이 초기화할 때

 

 

 

연산자 오버로딩, C++의 약속

객체도 기본 자료형 변수처럼 취급하여 덧셈, 뺄셈, 곱셈 등의 연산을 가능하게 한다.

operator 키워드와 연산자를 묶어서 함수의 이름을 정의하면 함수의 이름을 이용한 호출과 연산자를 이용한 함수의 호출을 허용한다.

pos1, pos2가 기본 자료형 변수가 아닌 객체여도 pos1 + pos2는 pos1.operator+(pos2)라는 문장으로 바꿔 해석한다. 
객체를 피연산자로 사용한 연산자의 이름 앞에 operator라는 이름을 붙여서 완성되는 이름의 함수를 호출하며 연산자 우측에 있는 피연산자를 인자로 전달

pos1-pos2;는 pos1.operator-(pos2);로 변환된다.
연산자 앞에 operator를 붙여서, operator-라는 이름의 함수를 호출. 연산자 왼쪽의 pos1객체를 대상으로 operator-함수를 호출, -연산자의 우측에 있는 피연산자를 인자로 전달. 

 

 

오버로딩 된 연산자의 해석

pos1 + pos2
1   2	3

pos1.operator+(pos2)
1	2	3

 

1. 멤버함수를 호출할 객체
2. 함수의 이름
3. 함수의 전달인자

연산자를 오버로딩한 함수도 const로 선언하는 게 좋다. 덧셈연산은 연산자의 대상이 되는 피연산자 값의 변경이 아니고 새로운 연산의 결과를 만들어 내는 것이기 때문에 const 선언이 가능하다.

 

 

연산자 오버로딩의 두 가지 방법

1. 멤버함수에 의한 연산자 오버로딩
2. 전역함수에 의한 연산자 오버로딩

 

pos1+pos2;가 멤버함수로 오버로딩되면  pos1.operator+(pos2);

 pos1.operator+(pos2);


pos1+pos2;가 전역함수로 오버로딩되면  operator+(pos1, pos2);

operator+(pos1, pos2);

 

동일한 자료형을 대상으로 + 연산자를 전역함수 기반, 멤버함수 기반으로 동시에 오버로딩 하면 멤버함수 기반으로 오버로딩된 함수가 전역함수 기반으로 오버로딩된 함수보다 우선으로 호출 (가급적 이런 상황은 만들지 x)

 

 

 

전역함수 기반으로 연산자 오버로딩
// GFunctionOverloading.cpp

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x=0, int y=0): xpos(x), ypos(y)
    { }
    void ShowPosition() const //내부출력문 const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
    friend Point operator+(const Point& pos, const Point& pos2);
    // 아래 operator+ 함수에 대해 private 접근을 허용하기 위한 friend 선언
};

// +연산자를 전역함수의 형태로 오버로딩
// Point 클래스는 operator+ 함수에 대해 freind선언을 하여 
// 함수 내에서는 Point 클래스의 private 멤버에 접근 가능
Point operator+(const Point& pos1, const Point &pos2) 
{
    Point pos(pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos);
    return pos;
}

int main()
{
    Point pos1(3, 4);
    Point pos2(10, 20);
    Point pos3 = pos1 + pos2; // +연산자가 전역함수의 형태로 오버로딩
    // operator+(pos1, pos2); 형태로 해석
    pos1.ShowPosition();
    pos2.ShowPosition();
    pos3.ShowPosition();

    return 0;
}

 

 

객체지향이기 때문에 특별한 경우가 아니면 멤버함수 기반으로 연산자를 오버로딩하는 게 낫다.
(예외는 아래에서 다룸)

 

 

 

오버로딩이 불가한 연산자의 종류

C++의 문법규칙 보존을 위해 아래 연산자들의 오버로딩을 제한한다.

1. 멤버 접근 연산자 (.)
2. 멤버 포인터 연산자(.*)
3. 범위 지정 연산자 (::)
4. 조건 연산자 (3항연산자 ?:)
5. sizeof (바이트 단위 크기 계산)
6. typeid (RTTI 관련 연산자) 
7. static_cast (형변환 연산자)
8. dynamic_cast (형변환 연산자)
9. const_cast (형변환 연산자)
10. reinterpret_cast (형변환연산자)

※ RTTI (runtime type information) :  RTTI는 C++ 컴파일러 내에 포함되어 있는 기능으로서, 객체의 유형을 실행 시에 결정할 수 있도록 허용한다. C++이나 다른 고급언어의 컴파일러에서 가지고 있는 이 기능은, 메모리 상주 객체에 유형 정보를 추가한다. 이렇게 함으로써, 실행 시스템은 객체의 캐스트가 유효한 것인지를 확실히 하기 위해, 그 객체가 특정 유형인지를 결정할 수 있다. 객체 기술의 기본 원리 중 하나가, 실행시 객체를 동적으로 변화시킬 수 있는 능력인 polymorphism이다. (출처로 이동)

 

 

멤버함수 기반으로만 오버로딩이 가능한 연산자

1. 대입 연산자 (=)
2. 함수 호출 연산자(())
3. 배열 접근 연산자([])
4. 멤버 접근을 위한 포인터 연산자 (->)

 

 

연산자 오버로딩 시 주의사항

1. 본래의 의도를 벗어난 형태의 연산자 오버로딩은 좋지 않다.
2. 연산자의 우선순위와 결합성은 바뀌지 않는다.
3. 매개변수의 디폴트 값 설정이 불가능하다.
- 피연산자의 자료형에 따라서 연산자를 오버로딩 한 함수의 호출이 결정되어 불가능하다.
- 매개변수의 디폴트 값이 설정되면 함수 호출관계가 불분명해진다.

4. 연산자의 순수 기능까지 빼앗을 수는 없다.
- int 데이터의 + 연산은 의미가 정해져 있고 그를 변경하는 아래와 같은 함수 정의는 허용되지 않는다.

int operator+(const int num1, const int num2) //정의 불가능
{
    return num1*num2;
}

 

 

※ 연산자 오버로딩 명칭의 이유

함수가 오버로딩되면 전달되는 인자에 따라 호출되는 함수가 달라지고, 이와 유사하게 연산자가 오버로딩되면 피연산자의 종류에 따라 연산의 방식이 달라지므로 연산자 오버로딩이라고 부르는 것이다.

int num = 3 + 4;
Point pos3 = pos1 + pos2; //pos1, pos2는 Point형 객체

 

 

 

 

 

10-2 단항 연산자의 오버로딩

이항연산자와 단항연산자는 피연산자의 개수가 2개, 1개로 다르고 이에 따른 오버로딩 차이점은 매개변수 개수이다.

 

증가, 감소 연산자의 오버로딩

단항 연산자로 ++, --이 있다. 1씩 증감하는 연산자다.
Point클래스에 ++ 연산자가 오버로딩 되어있다면 다음과 같은 문장이 가능하다.

++pos; //pos는 클래스객체
--pos;

호출되는 함수의 이름은 ++연산자와 키워드 operator를 연결해서 완성하여 operator++가 된다.
멤버함수의 형태로 오버로딩되면 pos의 멤버함수가 호출되는 형태이니 이렇게 해석된다.

pos.operator()++;

단항연산자를 오버로딩하여 전달인자는 없다! (하나 있는 피연산자의 멤버함수 호출하는 형태)
전역함수로 오버로딩된 경우 operator++가 전역함수의 이름이 되므로 이렇게 해석된다.
전역함수는 피연산자가 모두 인자로 전달된다.

operator++(pos); //전역함수는 피연산자가 모두 인자로 전달

 

++pos; 가 멤버함수로 오버로딩되면 pos.operator+();
++pos; 가 전역함수로 오버로딩되면 operator++(pos);

 

 

단항연산자 오버로딩 예제 :: 멤버함수 기반, 전역함수 기반으로 증가, 감소연산자를 오버로딩
// OneOpndOverloading.cpp 

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x = 0, int y = 0) : xpos(x), ypos(y) //멤버이니셜라이져
    {   }
    void ShowPosition() const //내부출력 const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
    Point& operator++() //++연산자가 멤버함수의 형태로 오버로딩 
    {
        xpos += 1;
        ypos += 1;
        return *this; 
        // this : 객체 자신의 포인터 값
        // this* 는 객체 자신을 의미, 객체 자신을 반환 
        // 
        // 함수의 반환형이 참조형으로 선언되어 객체 자신을 참조할 수 있는 참조값이 반환        
        // 반환형이 Point형으로 선언됐다면 객체 자신의 복사본을 만들어서 반환 (여기서는 복사 안일어남)
        // 일반적인 ++연산자처럼 연산이 가능하게 하려고 참조값을 반환한다
    }
    friend Point& operator--(Point &ref); //아래 정의된 전역함수에 대해 friend 선언
};

Point& operator--(Point& ref) //--연산자가 전역함수의 형태로 오버로딩
{
    ref.xpos -= 1;
    ref.ypos -= 1;
    return ref; //전달된  pos객체를 참조자ref로 받아서 다시 참조형으로 반환한다.
}

int main()
{
    Point pos(1, 2);
    ++pos;  //멤버함수의 형태로 오버로딩 되었으므로 이 문장은 pos.operator++(); 로 해석    
    pos.ShowPosition();
    
    --pos;  //전역함수의 형태로 오버로딩 되었으므로 이 문장은 operator--(pos); 로 해석
    pos.ShowPosition();

    ++(++pos);  
    // -> ++(pos.operator++()); pos객체의 멤버변수 값은 1 증가하고 pos의 참조 값이 반환
    // -> ++(pos의 참조 값);    
    // -> (pos의 참조값).operator++(); 와 같다
    // pos의 참조값은 pos객체를 대상으로 하는 연산과 같다
    // 결론은 pos 멤버변수가 2 증가한다

    pos.ShowPosition(); //출력

    --(--pos);  
    // --(operator--(pos)); 
    // --(pos의 참조 값);    
    // -> operator--(pos의 참조값); 와 같다

    pos.ShowPosition(); //출력

    return 0;
}

 

 

 

++(++pos); -> ++(pos.operator++( )); -> ++(pos의 참조 값); -> (pos의 참조 값).operator++( );
--(--pos); -> --(operator--(pos)); -> --(pos의 참조 값); -> operator--(pos의 참조 값);

 

 

 

전위증가와 후위증가의 구분

++연산자와 --연산자는 피연산자의 위치에 따라 의미가 달라진다.

num++ // 출력 후 num값 증가
++num // num값 증가 후 출력

앞의 예제는 전위증가/전위감소 연산 형태였고 이제는 후위증가/후위감소 연산에 대한 연산자 오버로딩을 알아본다.
C++에서 전위/후위 연산에 대한 해석방식은 규칙으로 정해두고 있다.

++pos -> pos.operator++();
pos++ -> pos.operator++(int);

--pos -> pos.operator--();
pos-- -> pos.operator--(int);

키워드 int를 이용해서 후위연산에 대한 함수를 전위연산에 대한 함수와 구분하고 있다.
int는 후위연산 구분용이지 데이터를 인자로 전달하는 목적이 아니다.

 

 

후위증가 후위감소 연산자 오버로딩 예
// PostOpndOverloading.cpp  

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x = 0, int y = 0) : xpos(x), ypos(y) //멤버이니셜라이져
    {}
    
    void ShowPosition() const //내부출력 const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
    
    // 전위증가
    Point& operator++() //++연산자가 멤버함수의 형태로 오버로딩
    {
        xpos += 1;
        ypos += 1;
        return *this; 
        // this는 객체 자신의 포인터 값
        // this* 는 객체 자신
        // 객체 자신을 반환
        // 
        // 함수의 반환형이 참조형으로 선언되어
        // 객체 자신을 참조할 수 있는 참조값이 반환

        // 반환형이 Point형으로 선언됐다면 객체 자신의 복사본을 만들어서 반환
    }

    //후위증가 -- int 매개변수로 구분!
    const Point operator++(int)
    {
        const Point retobj(xpos, ypos); //const Point retobj(*this);
        // 값의 증가에 앞서 반환에 사용할 복사본을 만든다
        // 값이 변경되면 안되서 const 선언
        // 물론 복사생성자 호출 형태로도 구현이 가능
        
        xpos += 1;
        ypos += 1;  // 멤버 값이 증가하기 이전에 만들어둔 복사본을 반환. 후위증가
        return retobj;
    }
    friend Point& operator--(Point &ref);
    friend const Point operator--(Point &ref, int); //후위감소
};

Point& operator--(Point &ref) //전위감소
{
    ref.xpos -= 1;
    ref.ypos -= 1;
    return ref;
}

// --를 후위증가 형태로 연산자를 전역함수 형태로 오버로딩
// 매개변수 선언에 int 를 추가하여 후위증가 명시
const Point operator--(Point &ref, int) // 후위감소
{
    const Point retobj(ref); // const 객체
    ref.xpos -= 1;
    ref.ypos -= 1;
    return retobj;
}

int main()
{
    Point pos(3, 5);
    Point cpy;
    cpy = pos--;
    cpy.ShowPosition();
    pos.ShowPosition();

    cpy = pos++;
    cpy.ShowPosition();
    pos.ShowPosition();
    return 0;
}

 

 

 

연산자오버로딩 예제 분할컴파일 (분리컴파일) 자체 실습

분할컴파일 연습을 해볼 겸 연산자오버로딩 예제 코드를 헤더와 cpp로 분리해보았습니다.
분할컴파일 연습으로 얻을 수 있는 것 : 반환형에 대한 정확한 이해

 

Point.h

#pragma once

/**
*
* @brief 후위증가 예시
* @details 연산자오버로딩
* @author lee
* @date 2021-11-23
* @version 0.0.1
*
*/

// TODO: 없음.20120
class Point
{
private:
    int xpos, ypos;
public:    
    Point(int x = 0, int y = 0);
    virtual ~Point();    
    void ShowPosition() const;      //내부출력 const    
    
    Point& operator++()             //전위증가
    {
        xpos += 1;
        ypos += 1;
        return *this; // 함수의 반환형이 참조형으로 선언되어 객체 자신을 참조할 수 있는 참조값이 반환
    } 

    const Point operator++(int);    //후위증가 - int 매개변수로 구분!
    
    friend Point& operator--(Point& ref); //전위감소
    friend const Point operator--(Point& ref, int);     //후위감소
};

 

Point.cpp

#include <iostream>
#include "Point.h"

using namespace std;

Point::Point(int x , int y)
	: xpos(x)
    , ypos(y) //생성자, 멤버이니셜라이져
{ 
    
}

Point::~Point()
{

}

void Point::ShowPosition() const //내부출력 const
{ 
	cout << '[' << xpos << ", " << ypos << ']' << endl; 
}
 
// Point& operator++()     전위증가 -- 정의도 h파일에 함

//point클래스의 멤버함수 
const Point Point::operator++(int) //후위증가 -- int 매개변수로 구분! 
{   //복사되어 들어옴
    const Point retobj(xpos, ypos); //const Point retobj(*this);
    // 반환에 사용할 복사본은 변경되면 안되서 const
    xpos += 1;
    ypos += 1;
    return retobj;  //멤버 값이 증가하기 전에 만들어뒀던 복사본 반환
}

Point& operator--(Point& ref) //전위감소
{
    ref.xpos -= 1;
    ref.ypos -= 1;
    return ref;
}

// 반환형 const는 함수의 반환으로 인해 생성되는 임시객체가 const로 생성 
// const 함수 내에서는 const 함수의 호출만 허용하도록 제한한다. 값의 변경유무와 별개로도 
// const 객체를 대상으로 참조자를 선언할 때 참조자도 const 선언해야 한다.
const Point operator--(Point &ref, int) // 후위감소 (전역함수 형태로 오버로딩)
{
    const Point retobj(ref); // 함수 내에서 retobj의 변경을 막는다 (후위증감 구현)
    //Point retobj(ref); // 함수 내에서 retobj의 변경을 막는다 (후위증감 구현) 
    ref.xpos -= 1;
    ref.ypos -= 1;
    return retobj; 
}

 

main.cpp

#include <iostream>
#include "Point.h"

int main()
{
    Point pos(3, 5);
    Point cpy;
    cpy = pos--;
    cpy.ShowPosition();
    pos.ShowPosition();

    cpy = pos++;    
    cpy.ShowPosition();
    pos.ShowPosition();

    //(pos++)++; //컴파일에러
    //(pos--)--; //컴파일에러
    // -> (Point형 const 임시객체)--;    
    // -> operator--(Point형 const 임시객체(상수니까)) 호출
    // 
    // const 객체 대상으로 const 선언되지 않은 멤버함수의 호출이 불가능하다
    // 
    // operator++ 멤버함수는 const로 선언된 함수가 아니고 (?잘모르겠음)
    // operator-- 전역함수는 매개변수로 선언된 참조자가 const 선언이 되지 않아서
    // 컴파일 에러가 발생한다
    
    cpy.ShowPosition();

    return 0;
}

 

 

 

반응형

 

 

10-3 교환법칙 문제의 해결

교환법칙은 'A+B의 결과는 B+A 결과와 같음'을 뜻한다.
연산자를 중심으로 한 피연산자의 위치는 연산결과에 영향이 없다.
교환법칙 성립 연산은 곱셈, 덧셈연산이 있다.

 

자료형이 다른 두 피연산자를 대상으로 연산

연산자 오버로딩으로 다른 자료형 데이터 2개를 연산하기
형변환 규칙에 따라 변환이 진행된 후 연산이 이루어진다.

 

Point.h

#pragma once
class Point
{
public:
	Point(int x = 0, int y = 0);
private:
	int xpos, ypos;
public:
	void ShowPosition() const;
	Point operator*(int times);
};

 

Point.cpp

#include <iostream>
#include "Point.h"
using namespace std;

Point::Point(int x, int y)
	: xpos(x), ypos(y)
{	}

void Point::ShowPosition() const
{
	cout << '[' << xpos <<", "<<ypos<< ']' << endl;
}

// 곱셈 연산자 오버로딩으로 객체와 정수간의 곱셈이 가능하게 됨
Point Point::operator*(int times)
{
	Point pos(xpos*times, ypos*times);
	return pos;
}

 

Main.cpp

// PointMultipleOperation 

#include <iostream>
#include "Point.h"

int main()
{
	Point pos(1, 2);
	Point cpy;

	cpy = pos*3; // pos.operator*(3) 로 해석되며 이 경우 Point객체가 곱셈연산자의 왼편에 와야한다
	// 교환법칙으로 3*pos;도 가능하게 하려면 오버로딩한 곱셈연산자를 바꿔야 한다.
	cpy.ShowPosition();

	cpy = pos*3*2; // 3을 곱했을 때 반환되는 객체를 대상으로 다시 2를 곱하고 결과가 cpy에 저장된다
	
	cpy.ShowPosition();

	return 0;
}

 

 

교환법칙 성립을 위한 구현

cpy = 3 * pos; 가 교환법칙이 성립되게 하려면 전역함수로 곱셈연산자를 오버로딩해야 한다.
cpy = operator*(3, pos); 처럼 해석되도록 연산자를 오버로딩 해야하고,
operator* 함수를 둘 중 하나로 정의해야 한다.

Point operator*(int times, Point& ref)
{
	Point pos(ref.xpos * times, ref.ypos * times);
	return pos;
}
Point operator*(int times, Point& ref)
{
	 return ref*times;
}

 

 

Point.h

#pragma once
class Point
{
public:
	Point(int x = 0, int y = 0);
private:
	int xpos, ypos;
public:
	void ShowPosition() const;
	Point operator*(int times); //곱셈연산자오버로딩
	friend Point operator*(int times, Point& ref); //교환법칙
};

 

Point.cpp

#include <iostream>
using namespace std;
#include "Point.h"

Point::Point(int x, int y)
		: xpos(x),
		ypos(y)
{	}

void Point::ShowPosition() const
{
	cout << '[' << xpos << ", " << ypos << ']' << endl;
}

Point Point::operator*(int times)
{
	Point pos(xpos * times, ypos * times);
	return pos;
}

// 교환법칙
Point operator*(int times, Point& ref)
{
	return ref*times;
}

 

Main.cpp

// CommunMultipleOperation 

#include <iostream>
using namespace std;
#include "Point.h"

int main()
{
	Point pos(1, 2);
	Point cpy;

	cpy = 3 * pos;
	cpy.ShowPosition();

	cpy = 2 * pos * 3;
	cpy.ShowPosition();

	return 0;
}

 

 

 

 

10-4  cout, cin  그리고 endl의 정체

cout과 endl

콘솔 입출력에 사용되는 키워드 cout과 endl을 유사하게 흉내내서 이해를 돕는 예제이다.
주어진 예제를 특성에 맞게 파일별로 분리해서 실습했다.

 

mystd.h

#pragma once
/**
*   cout과 endl 흉내내기를 위해 만든 mystd namespace
*   namespace는 소속을 정해 이름을 붙여 구분한다
*   이름이 같아 생길 수 있는 충돌을 방지한다
*/

namespace mystd
{
    using namespace std; // using namespace 이름;은 네임스페이스 모든 요소에 이름 없이 접근 가능
    // 함수 내부에서 선언해서 적용 범위는 함수 스코프 한정
    // printf 함수 호출을 위해 삽입  

	class ostream
	{
	public:
		void operator<<(char* str);	 // 문자열 출력
		void operator<<(char str);	 // 문자 출력
		void operator<<(int num);	 // 숫자 출력
		void operator<<(double e);   // 소수점 출력
		void operator<<(ostream& (*fp)(ostream& ostm));  // ??
	};
}

 

ostream.cpp

#include <stdio.h> 
#include "mystd.h"


namespace mystd
{
	void ostream::operator<<(char* str)
	{
		printf("%s", str);
	}

	void ostream::operator<<(char str)
	{
		printf("%c", str);
	}

	void ostream::operator<<(int num)
	{
		printf("%d", num);
	}

	void ostream::operator<<(double e)
	{
		printf("%g", e);
	}

	// ostream& endl(ostream& ostm) 함수를 인자로 전달받음
	// 추가공부 : 함수포인터
	void ostream::operator<<(ostream& (*fp)(ostream& ostm))
	{
		fp(*this);
	}

	// endl 함수
	ostream& endl(ostream& ostm)
	{
		ostm << '\n';
		fflush(stdout); //버퍼 비우기
		return ostm;
	}

	ostream cout; //객체의 이름 
	// 객체 내에서 다양한 기본자료형 데이터를 대상으로 << 연산자 오버로딩을 하고 있다.

}

 

 

main.cpp

// ConsoleOutput : cout과 endl 이해

#include <iostream>
#include "mystd.h" 

int main()
{
	// using 네임스페이스이름::요소 는
	// 네임스페이스 이름 없이도 사용하게 해줌
	// 지역적 using 선언
	using mystd::cout;
	using mystd::endl;

	cout << "Simple String";
	cout << endl;
	cout << 3.14;
	cout << endl;
	cout << 123;
	endl(cout);

	return 0;
}

 

 

아래처럼 연이은 <<연산을 진행하려면 <<연산 결과 cout이 반환되어야 한다. 그래서 예제를 수정해본다.

cout<<123<<endl<<3.14<<endl;

 

 

IterateConsoleOutput.cpp
// IterateConsoleOutput  

#include <iostream> 

namespace mystd
{
	using namespace std; // using namespace 이름;은 네임스페이스 모든 요소에 이름 없이 접근 가능
	// 함수 내부에서 선언해서 적용 범위는 함수 스코프 한정
	// printf 함수 호출을 위해 삽입  

	class ostream
	{
	public: 
		//void operator<<(const char* str)
		ostream& operator<<(char* str) //cout 객체의 참조값을 반환
		{
			printf("%s", str);
			return *this;
		}

		ostream& operator<<(char str)
		{
			printf("%c", str);
			return *this;
		}

		ostream& operator<<(int num)
		{
			printf("%d", num);
			return *this;
		}

		ostream& operator<<(double e)
		{
			printf("%g", e);
			return *this;
		}

		ostream& operator<<(ostream& (*fp)(ostream& ostm))
		{
			return fp(*this);
		}
	};


	// 원래 endl 처럼 개행하고 출력버퍼를 비우는 작업을 해 준다
	// 반환된 값을 재 반환하는 형태로 오버로딩
	ostream& endl(ostream& ostm)
	{
		ostm << '\n';
		fflush(stdout); //버퍼 비우기
		return ostm;
	}

	ostream cout; //객체의 이름. 객체 내에서 다양한 기본자료형 데이터를 대상으로 << 연산자 오버로딩을 하고 있다.

}

int main(void)
{
	// using 네임스페이스이름::요소 는
	// 네임스페이스 이름 없이도 사용하게 해줌
	// 지역적 using 선언
	using mystd::cout;
	using mystd::endl;

	cout << 3.14 << endl << 123 << endl;

	return 0;
}

 

 

<<, >> 연산자 오버로딩

<<연산자에 이어 >>연산자도 오버로딩 해 본다.

 

* 구현 전 알아야 할 것

- cout은 ostream 클래스의 객체
- ostream 은 이름공간 std 안에 선언되어 있으며, 헤더파일 <iostream>을 포함해야 한다.
- 멤버함수 형태로 오버로딩하면 cout.operator<<(pos)형태로 해석이 가능해야 한다.
- 전역함수 형태로 오버로딩하면 operator<<(cout, pos)형태로 해석이 가능해야 한다.

cout객체의 멤버함수를 추가하는 것 말고 전역함수에 의한 방법으로 구현한다.

// PointConsoleOutput

#include <iostream>
using namespace std;

class Point
{
public:
	Point(int x=0, int y=0) 
		: xpos(x),
		ypos(y)
	{	}
private:
	int xpos, ypos;
public:
	void ShowPosition() const
	{
		cout << '[' << xpos << ", " << ypos << ']' << endl;
	}
	friend ostream& operator<<(ostream&, const Point&);
};

ostream& operator<<(ostream& os, const Point& pos)
{
	os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;\
	// 인자로 전달된 cout의 참조자를 통한 출력을 구성
	return os;
}

int main(void)
{
	Point pos1(1, 3);
	cout << pos1; //ostream& operator<<(ostream& os, const Point& pos)가 호출된다
	// 연산자 우측이 Point객체
	Point pos2(101, 303);
	cout << pos2;
	return 0;
}

 

 

* 추가

- cin은 istream 클래스의 객체이다
- isteram은 이름공간 std 안에 선언되어 있으며, 헤더파일 <iostream>을 포함해야 한다.

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v