[C++] std::endl이 무엇인가

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);
}

이 연산자는 함수를 인수로 전달받기 위해, 함수 포인터를 매개변수로 사용합니다.

이러한 함수 포인터에 관한 내용은 여기에서 볼 수 있습니다.

 

[C++] 함수 포인터 개념 및 사용하는 이유

함수 포인터란 무엇인가?이전 글에서 포인터를 이렇게 설명했습니다. 포인터란 객체의 메모리 주소를 저장하는 변수입니다.여기서 객체란 변수, 배열, 구조체, 클래스, 함수 등 메모리에

codingembers.tistory.com

 

이제,   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

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유