IT Language 연습실
cout, endl, cin의 정체 본문
cout과 endl 그리고 cin의 경우 표준입출력 라이브러리에 정의된 iostream 이라는 것을 불러와 사용할 수 있었다.
연산자 오버로딩을 공부하고 연산자 오버로딩을 공부하면서 알게된 cout endl cin의 정체에 대해서 알아볼까한다.
우리는 어떤 수나, 문자를 출력하기 위해서 cout 이라는 것을 사용하곤 한다.
그리고 어떤 수나, 문자를 출력하고 다음 행을 바꾸기 위해 endl 라는 것을 사용하고
어떤 수나, 문자를 입력하기 위해서 cin이라는 것을 사용하곤 한다.
이는 Cpp에 해당 하는 경우이다.
좀 더 구체적으로 예를 들어 출력을 위해서
cout << "Hello" ; 문자를 출력하고는 한다는 것이다.
그런데 연산자 오버로딩에 대해 공부하면서 << 이라는 연산자는 shfit left 비트 연산이다. 즉, 우측에 있는 값을 좌측으로 이동 이동하는 연산이라고 봐도 되는데.
그동안 cout의 정체를 모를때는 cout 이라는 것 자체가 출력문이라서 출력이 되는 구나로만 넘어갔었다.
하지만 cout이라는 녀석도 한 클래스의 객체이다.
iostream 이란 헤더파일에 정의된 녀석을 들여다보면
std 이란 이름공간으로 묶인 ostream 클래스의 객체라는 사실을 알 수 있었다.
즉 ostream 클래스의 객체가 cout 이름으로 생성이 된 것이다.
cout << "Hello" ; 이 문장에 그럼 더 의문이 생길 것이다 객체에 문자를 ? 연산할 수 없다.
그럼 연산자 오버로딩을 통하여 연산되는 것임을 어느정도 짐작할 수 있는데 그것을 자세히 살펴보면 다음과 같이 동작하고 있음을 알 수 있다.
cout << "Hello" ; 라는 코드를 입력하고 Hello 라는 문자열이 출력되는 이유는
#include <iostream>
namespace mystd {
class ostream {
public :
void operator<<(const char *str) {
printf ("%s", str);
}
};
ostream cout;
}
int main( ) {
mystd::cout<<"Hello";
}
위 코드와 같이 동작하기 때문이다.
위 코드는 std안에 정의 되어있는 cout이 정의되고 동작되는 과정을 예시로 들기 위해 코드로 구현해 본 것이다.
따라서 mystd라는 것이 사용됐고 ostream이라는 클래스가 정의됐다.
결국 위 코드를 분석해보면
cout.operator(Hello) 라는 것이 되고 Hello가 인자로 전달되어 str이 받고 그 str을 출력하여 cout이 출력되는 결과를 보게 되는데 이것이 실질적으로 std 소속된 cout의 동작과정이다.
즉 iostream 헤더 파일에 정의된 cout 은 위와 같이 연산자 오버로딩을 사용하고 있음이다!!
endl 같은 경우는 어떻게 줄 바꿈을 할 수 있는 것일까?
endl은 ostream에서 함수로 정의되어 있다. 즉, 따라서 객체 << 함수 라는 형태인데
위 형태를 보면 이 역시 연산자 오버로딩을 통하여 동작하고 있음을 예상할 수 있다.
cout.operator<<(endl) 의 형태로 우선 연산자 오버로딩이 동작될 것이고 함수가 인자로 전달되는 것을 보니
예상할 수 있는 것이 또 하나 있다. 함수를 인자로 전달한다? 그러면 함수를 받을 수 있는 포인터 변수가 있을 것이고
결국 포인터 함수가 나와야 한다. (포인터 함수에 대한 이해를 어느정도 하고 있어야만 한다.)
그렇다면 endl 이 정의된 함수의 반환형과 매개변수 타입, 개수 가 같은 포인터 함수가 정의되어 있을 것이다.
다음 코드와 같이 말이다.
#include <iostream>
namespace mystd {
class ostream {
public :
void operator<<(const char *str) {
printf ("%s", str);
}
void operator<<(char str) {
printf ("%c", str);
}
void operator<<(ostream & (*fp)(ostream &)) {
fp(*this);
}
};
ostream cout;
ostream & endl(ostream & os) {
os<<'\n';
fflush(stdout);
return os;
}
}
int main( ) {
mystd::cout<<"Hello";
mystd::cout<<mystd::endl;
mystd::cout<<"world";
}
위 코드를 보면 포인터 함수를 통해서 endl 함수의 인자로 *this 가 전달되고 있다.
그런데 *this 는 cout이 된다.
즉 cout을 전달하게 된 것이고 os는 참조자가 되어 cout 을 참조하게 된다.
따라서 os << '\n' 은 연산자 오버로딩 과정에서 void operator<<(char str) { printf ("%c", str) ; } 호출한다.
fflush(stdout) 버퍼를 비운다.
그렇기 때문에 개행으로써 동작이 되는 것이다.
그런데 사실 위 코드들은 미완성이다.
출력문을 작성할때 cout<<"Hello"; cout<<endl; cout<<"world"; 라는 문으로도 작성이 되지만.
cout<<"Hello"<<endl<<world<<endl; 라고 한번에 작성하기도 하고
cout<<"Hello"<<' '<<world<<endl; 라고 띄어쓰기만 한번 하고 이어서 출력 되기도 한다.
하지만 위 코드 들로는 cout<<"Hello"<<endl<<world<<endl; 이나 cout<<"Hello"<<' '<<world<<endl; 출력할 수 없다.
컴파일 에러가 난다.
그것은 반환할때의 문제가 생기는 것이다.
cout<<"Hello"에 대해서 연산자 오버로딩으로 작업을 하고 보면 반환이 void 없다. 그렇기 때문에
다음 오버로딩을 할 수 없게 되는 것이다.
따라서 반환문을 cout이 될 수 있게 해줘야한다.
#include <iostream>
namespace mystd {
class ostream {
public :
ostream & operator<<(const char *str) {
printf ("%s", str);
return *this;
}
ostream & operator<<(char str) {
printf ("%c", str);
return *this;
}
ostream & operator<<(ostream & (*fp)(ostream &)) {
return fp(*this);
}
};
ostream cout;
ostream & endl(ostream & os) {
os<<'\n';
fflush(stdout);
return os;
}
}
int main( ) {
mystd::cout<<"Hello" << ' ' << "world" <<mystd::endl;
}
자 이렇게 해서 cout과 endl의 동작과정을 살펴보았다.
이 cout은 객체 그리고 endl은 함수로써 iostream의 ostream의 객체 또는 외부함수로 정의되고 호출하여 동작한다.
cin은 cout의 반대이다 cout 이 ostream의 객체라면 cin istream의 객체로 생성되어 있고
입력을 받는 것은 scanf로 받거나 할 것이다.
#include <iostream>
using namespace std;
class Point {
int x; int y;
public :
Point (int num =0, int num2=0) : x(num), y(num) {}
friend istream & operator>>(istream &, Point &);
friend ostream & operator<<(ostream &, Point &);
};
istream & operator>>(istream & is , Point & pos) {
is>>pos.x>>pos.y; // scanf("%d %d", &pos.x, &pos.y); 가능하다.
return is;
}
ostream & operator<<(ostream & os, Point & pos) {
cout<<"["<<pos.x<<","<<pos.y<<"]"<<endl;
return os;
}
int main() {
Point pos1;
cout<<"x, y 좌표 순으로 입력 : ";
cin>>pos1; /// operator>>(cin ,pos)
cout<<pos1;
Point pos2;
cout<<"x, y 좌표 순으로 입력 : ";
cin>>pos2; /// operator>>(cin ,pos)
cout<<pos2;
}
위는 실질적으로 cin이 동작되는 과정을 구현한 코드보다는
iostream에 정의된 istream 클래스의 객체와 ostream클래스의 객체를 사용하는 예를 보이고
Point라는 클래스의 pos1 객체를 생성하여
cin>>pos1 이나 cout<<pos1 의 객체의 연산을 수행하기 위해서 어떻게 operator를 정의해야 하는지를 보이는 예시이다.
즉, cin이 istream의 객체이고 pos1의 객체이라면 이것은 >> 연산자 오버로딩이 되어
cin.operator>>(pos1)이 되어야 하나 문제는 istream이나 ostream 의 정의된 연산자 오버로딩 operator는
문자, 문자열, 정수, 실수 뿐이 정의가 안되어있고 클래스 객체나 구조체에 대한 정의가 되어 있지 않는다.
따라서 cin.operator>>(pos1)이 되려면 iostream 헤더파일에 가서 따로 operator 를 정의해줘야 한다.
하지만 이는 굉장히 복잡하고 어려움이 있다.
그렇기 때문에 멤버함수 기반의 연산자 오버로딩 과정은 수행할 수 없다.
따라서 전역함수 기반의 연산자 오버로딩 과정을 수행한 것이다.
또한 전역함수 기반의 연산자 오버로딩 과정을 수행하면서 클래스 내에 있는 멤버변수에 접근을 해야 하기에
friend 키워드를 사용하여 접근할 수 있도록 한것이다.
'C++' 카테고리의 다른 글
template (2) 함수 ver (0) | 2024.02.21 |
---|---|
template (1) 함수 ver (0) | 2024.02.21 |
상속(3) (0) | 2024.02.13 |
상속(2) (0) | 2024.02.13 |
상속(1) (0) | 2024.02.13 |