std::cout의 동작 방식
std::cout은 std::ostream 클래스 타입의 전역 객체입니다.
참고로, cout는 character output의 약자입니다.
그리고, std::ostream은 프로그램의 데이터를 화면( console )에 출력하는 기능을 갖고 있습니다.
여기서, std::ostream은 basic_ostream < char >의 별명으로 정의되어 있지만, 코드를 쉽게 읽을 수 있도록 basic_ostream이 나타나는 부분을 ostream으로 변경하겠습니다.
아래에, 이러한 std::cout 객체를 사용하는 방법을 보여줍니다.
#include <iostream> // for std::cout and std::endl
int main(){
using std::cout;
cout << "Hello, world!!" << std::endl;
cout << "New line starts";
}
▼출력
Hello, world!!
New line starts
여기서, std::endl을 사용하면, "Hello, world!!" 문자열 뒤에 개행을 출력한다는 것을 알고 있을 것입니다.
그럼, std::endl는 '\n' 의 별명( alias )일까요?
위의 예에서 다음 문장이 실행되는 과정을 따라가 봅시다.
cout << "Hello, world!!" << std::endl;
먼저, 위의 문장은 다음과 같은 순서로 실행됩니다.
( cout << "Hello, world!!" ) << std::endl;
즉, cout << "Hello, world!!" 코드가 먼저 실행되고, 이 코드는 ostream 클래스의 삽입 연산자 << 를 실행합니다.
이 연산자에서 입력된 문자열을 cout 객체가 가지고 있는 내부에 저장합니다.
cout 객체는 이 내부의 버퍼가 완전히 차기 전 또는 일정한 시간이 지날 때까진 저장된 데이터를 외부로 방출하지 않습니다.
그리고, 이 << 연산자는 cout 객체를 다시 반환하기 때문에 위의 문장은 아래와 같이 될 것입니다.
cout << std::endl;
이제, 이 코드가 어떻게 실행되는지를 알려면 std::endl가 무엇인지를 알아야 합니다.
이것이 무엇인지를 알기 위해, 표준 라이브러리를 따라가 보면 다음과 같은 코드를 만나게 됩니다.
( 설명을 위해 basic_ostream <...>을 ostream으로 변경하였습니다. )
inline ostream&
endl( ostream& __os ){
return flush(__os.put(__os.widen('\n')));
}
즉, std::endl은 템플릿 함수이며, 이 함수는 std::ostream 객체를 매개변수로 받아서, 이 객체가 가지고 있는 내부 버퍼에 개행문자 '\n' 을 추가하고, 이 내부 버퍼에 들어있는 모든 데이터를 외부로 방출( flush )하는 기능을 수행한다는 것을 알 수 있습니다.
그리고, 이 함수는 << 연산자를 연쇄적으로 호출할 수 있도록 다시 ostream 객체를 반환합니다.
std::endl이 함수이므로, ostream 클래스는 이 함수를 연산자 체인에 넣기 위해 다음과 같이 << 연산자를 오버로딩 합니다.
typedef ostream& (*manipulator)(ostream&); // 함수 포인터
ostream& operator<<( manipulator pf){ // 연산자 재정의
return pf(*this);
}
이 연산자는 함수를 인수로 전달받기 위해, 함수 포인터를 매개변수로 사용합니다.
이러한 함수 포인터에 관한 내용은 여기에서 볼 수 있습니다.
이제, cout << std::endl 코드가 실행되면, 위의 연산자가 호출되고, 이 연산자는 std::endl 함수를 호출하게 됩니다.
그리고, 이것이 다음과 같은 문장들을 가능하게 만드는 것입니다.
cout << "Hello, world!!" << std::endl;
bool bVal = true;
cout << std::boolalpha << bVal << std::endl;
▼출력
Hello, world!!
true
위의 std::boolalpha도 std::endl과 마찬가지로 함수이며, 이 함수는 bool 의 값을 true 또는 false 형식으로 출력할 수 있도록 std::cout 객체의 내부 플래그를 변경하는 기능을 수행합니다.
std::endl과 '\n'의 차이점
위에서 말했듯이, std::endl은 함수입니다.
이 함수는 두 가지 일을 하는데, 개행문자 '\n' 를 내부버퍼에 저장하고, 내부 버퍼를 모두 방출합니다.
그런데, cout의 내부 버퍼가 다 차지도 않았는데, 내부 버퍼를 방출한다는 것은, 다 차지도 않은 휴지통을 불필요하게 자주 비우는 것과 마찬가집니다.
따라서, 일반적인 경우에는 std::endl를 사용하는 것보다 '\n' 를 사용하는 것이 더 바람직합니다.
그리고, 심지어 코드를 입력하는 것도 이것이 더 간편합니다.
cout << "Hello, world!!\n";
bool bVal = true;
cout << std::boolalpha << bVal << '\n';
하지만, 다음과 같이 내부 버퍼를 강제로 방출해야 되는 경우도 있습니다.
#include <iostream>
int main(){
using std::cin;
using std::cout;
cin.tie(nullptr); // cout과 같은 스트림으로 묶지 않음
// '\n'을 사용하면 내부 버퍼가 방출되지 않습니다.
cout << "store datas in inner buffer\n";
std::string strInput;
cin >> strInput;
}
std::cin 객체와 std::cout 객체는 기본적으로 같은 스트림을 사용해서 출력하고, 입력받습니다.
그렇기 때문에, 출력을 하는 중에 입력을 받으려고 하면, 내부 버퍼를 방출한 후에, 입력받은 데이터를 내부 버퍼에 저장합니다.
그런데, 위와 같이 tie( nullptr ) 함수를 사용하면, cout과 cin 객체가 서로 다른 스트림을 사용해 출력하고 입력받게 됩니다.
즉, 출력해야 할 데이터가 아직 남아있는 중에 입력을 받을 수도 있게 될 수 있으므로, 이런 일이 없도록 하기 위해 직접 내부 버퍼를 방출하는 것이 필요한 때가 발생합니다.
이때 사용하면 되겠네요.
이 글과 관련 있는 글들
표준 출력 printf() 함수와 객체 std::cout
'C, C++ > 표준 라이브러리' 카테고리의 다른 글
[C++] 객체이면서 참조 역할을 하는 reference_wrapper (0) | 2024.09.29 |
---|---|
[C++ 17] 함수의 결과와 값을 동시에 알려주는 optional 객체 (0) | 2024.09.25 |
[C++] 오버로딩( overloading )을 통한 사용자 정의 데이터 입출력 (0) | 2024.09.24 |
[C++] 균일 초기화( uniform initialization )과 std::initializer_list (2) | 2024.08.31 |
[C++] getline 함수 사용법 (0) | 2024.08.24 |