삽질의 현장/- C++

#014_시(c)시(c)해서 C++?!_cout과 endl 흉내내기

shovelman 2015. 7. 13. 01:36

안녕하세요 삽잡이입니다...

 

이번시간에는 우리가 자주 사용하던 cout 기능의 메서드와 endl 을 알아보고자합니다.

 

cout은 iostream에 속해있는 메서드입니다. 

그 중에서 ostream 이라는 클래스에 포함되어있죠...


우리는 이 ostream 클래스 안에 있는 

cout이라는 친구를 사용하여 출력을 하고 있었습니다...


이전 시간까지 배워온 연산자 중복정의를 응용하여서

cout의 기능을 흉내내보려고 합니다... 비록 완벽하진 않지만

아.. cout은 이런식으로 구현되있구나라는 것을 맛만 봐보도록 하겠습니다...

대충 감이 오실걸요~? 확인해보도록 하죠...


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
namespace sap
{
    class sapostream
    {
    public:
        sapostream& operator<< (char* str)
        {
            printf("%s", str);
            return *this;
        }
        sapostream& operator<< (char str)
        {
            printf("%c", str);
            return *this;
        }
        sapostream& operator<< (int num)
        {
            printf("%d", num);
            return *this;
        }
        sapostream& operator<< (double e)
        {
            printf("%g", e);
            return *this;
        }
        sapostream& operator<< (sapostream& (*fp)(sapostream &ostm))
        {
            return fp(*this);
        }
    };
 
    sapostream& sendl(sapostream &ostm)
    {
        ostm << '\n';
        fflush(stdout);
        return ostm;
    }
 
    sapostream scout;
}
 
void main()
{
    using sap::scout;
    using sap::sendl;
 
    scout << "String"<< sendl<< 3.14<< sendl<< 123<< sendl;
    sendl(scout);
}
cs


자... 기존의 cout과 헷갈리지 않도록

sap이라는 새로운 namespace에 sapostream 클래스를 정의했습니다.

그 안에 scout 이라는 객체를 생성했고, sendl 이라는 메소드도 정의했습니다...


이를 통해서 cout과 endl 의 기능을 모방해봤습니다...

이전 시간까지 알아본 중복정의를 응용한 것이기 때문에 

문제 없이 이해하실 수 있으리라 생각합니다...


한가지 말씀드리고 싶은 것은


operator << 연산자의 반환형을 해당 클래스의 참조형인

sapostream& 으로 했다는 것입니다....


왜냐, 

scout << "String"<< sendl<< 3.14<< sendl<< 123<< sendl;

해당 문장을 수행하기 위해서는


( ( ( ( scout<<"String" ) << sendl ) << 3.14 ) << sendl ) << 123 ) << sendl );

이와 같이 왼쪽부터 하나하나 해결을 하며 나아가야되기 때문이죠....

즉, 연속적으로 scout이 나와야 된다는 뜻이기 때문에

scout 객체의 참조값 반환을 통해 연속적인 scout이 나오게 한 것입니다...


sendl 같은 경우에는 

해당 메소드 내에 개행 (\n) 및 버퍼를 비우도록 만들어 놨는데요...


<< 연산자 중복 정의에서 

함수를 인자로 받을 수 있도록 정의 해두었기 때문에 

sapostream 내 sendl을 인자로 전달 시 해당 함수를 수행하도록 했습니다...



아하... 이런식으로... cout과 endl이 만들어졌겠다는 

약간의 감이 잡히군요...


이를 통해 다시한번 연산자 중복 정의에 대해서 느낍니다...

사용하는 개발자를 위해 모든 조건이 충족된 중복 정의는 유익하겠군...

그 반대로 사용하는 개발자가 이해하지 못하는... 헛점이 많은 중복정의는

오히려 해롭겠다는... 쓸데없는 생각을 해보게 되었습니다...


아무튼... 중복정의... 알아둬서 나쁠 것이 전혀 없는거 같다는...




이번에는 기존에 cout에 Sap이라는 객체의 정보를 출력할 수 있도록

연산자를 중복정의(오버로딩) 해보겠습니다...


cout은 이미 ostream 라는 클래스의 객체입니다.

즉, cout은 우리가 정의하지 않았기에 멤버 함수로 중복 정의를 한다고 가정한다면

우리는 해당 클래스를 찾아서 추가를 해야하는 문제가 발생합니다...


따라서 우리는 전역함수를 통해 중복정의를 해야하는 방법으로 

이 문제를 해결해야합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <string>
using namespace std;
 
class Sap
{
    int num;
    string name;
 
    friend ostream &operator<<(ostream &os, const Sap &s);
    friend ostream &operator<<(ostream &os, const Sap *s);
public:
    Sap(int num, string name)
    {
        this->num = num;
        this->name = name;
    }
};
 
ostream &operator<<(ostream &os, const Sap &s)
{
    os << "번호: " << s.num << " 이름: " << s.name;
    return os;
}
ostream &operator<<(ostream &os, const Sap *s)
{
    os << "번호: " << s->num << " 이름: " << s->name;
    return os;
}
 
void main()
{
    Sap sap(4"삽잡이");
    cout << sap << endl;
 
    Sap* ssap = new Sap(2"솹잡이");
    cout << ssap << endl;
}
cs


Sap 이라는 클래스를 만들었습니다. 다시한번 말씀드리자면 

cout<<sap 은 cout.operator<<(sap); 이 될 수 없습니다...


왜냐, 이미 ostream 클래스에는 Sap이라는 형의 인자를 받는

operator << 메소드를 제공하고 있지 않기 때문입니다....


즉, 본인은 oprator<<(ostream &os, Sap &sap) 

이와같 은 전역 함수로 이 문제를 해결했습니다.


또한 private한 멤버들에 접근을 하기 위해서 부득이하게도 friend를 사용했습니다...


개발자가 정의한 형식 자체를 출력할 수 있게 해주는 것을

우리는 개체 출력자라고도 부를 수 있겠습니다...




자... 오늘은 여기까지~!

이상 삽잡이였습니다!