36-1-나.출력 스트림

표준 출력 객체는 cout이며 라이브러리에 의해 이름이 이미 정해져 있다. 물론 유니코드 문자열을 출력해야 한다면 cout 대신 wcout 객체를 사용하면 된다. cout으로 출력을 보낼 때는 << 연산자(Insertion)를 사용하는데 C++의 모든 기본 타입과 문자형 포인터에 대해 << 연산자가 오버로딩되어 있으므로 타입에 상관없이 보내기만 하면 << 연산자가 타입을 해석하여 적절하게 출력한다. 또한 << 연산자가 출력 객체에 대한 레퍼런스를 리턴하므로 연쇄적인 출력도 가능하다.

<< 연산자는 원래 비트를 왼쪽으로 이동시키는 쉬프트 연산자인데 ostream 클래스가 이 연산자를 오버로딩하여 삽입 연산자로 사용하고 있다. 이 연산자가 선택된 이유는 모양이 출력을 잘 표현하기 때문이며 그래서 직관적이다. 그러나 오버로딩된 연산자는 의미를 바꿔도 우선 순위나 피연산자의 개수 등은 원래 연산자의 것을 그대로 사용한다는 점을 주의해야 한다. 다음 예제를 보고 출력 결과를 예상해 보자.

 

: couterror

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     bool bMan=true;

 

     cout << "당신은 " << bMan==true ? "남자":"여자" << "입니다." << endl;

}

 

아주 짧은 소스이므로 쉽게 해석되며 뭘 출력하고 싶은 코드인지 쉽게 짐작이 갈 것이다. 그러나 실제로 컴파일해 보면 cout에 == 연산자가 정의되어 있지 않다는 에러로 처리되는데 삼항 조건 연산자보다 << 연산자가 우선 순위가 더 높기 때문이다. bMan을 cout으로 먼저 출력하고 리턴된 cout을 true와 상등 비교했으므로 틀린 연산이 된 것이다. 삼항 조건 연산문을 괄호로 싸서 먼저 연산하도록 하면 원하는 결과가 나온다.

 

cout << "당신은 " << (bMan==true ? "남자":"여자") << "입니다." << endl;

 

cout은 인수로 전달된 출력 대상을 내부적인 변환 규칙에 따라 문자열로 변환하여 화면으로 출력한다. 예를 들어 정수 123은 문자열 "123"으로 실수 4.567은 "4.567"로 변환할 것이다. 만약 이런 내부적인 변환 규칙에 변화를 주고 싶다면 조정자(manipulator)를 사용한다. 조정자는 cout이 값을 출력하는 방식에 여러 가지 변화를 주는 역할을 한다. 먼저 정수의 진법을 변경해 보자.

 

: coutradix

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     int i=1234;

 

     hex(cout);

     cout << i << endl;

 

     cout << "8진수 : " << oct << i << endl;

     cout << "16진수 : " << hex << i << endl;

     cout << "10진수 : " << dec << i << endl;

}

 

정수 i를 별다른 지정없이 cout << i;로 보내면 10진수로 출력되는데 8진수나 16진수로 바꿔서 출력할 수 있다. hex, oct, dec 등의 전역 함수는 인수로 주어진 출력 객체의 진법을 변경하는데 한 번 변경해 놓으면 이후부터 출력 객체는 지정한 진법대로 출력한다. 예를 들어 hex(cout)로 cout의 출력 방식을 16진법으로 바꾼 후 i를 출력하면 16진수로 출력될 것이다.

hex, oct, dec 등의 함수를 직접 호출하는 대신 << 연산자로 함수를 cout으로 보내도 마찬가지 효과가 있다. << 연산자가 함수 포인터에 대해서도 오버로딩되어 있어 전역 함수를 대신 호출하도록 되어 있기 때문이다. << 연산자가 함수이고 인수로 함수 포인터가 전달되었으므로 전달된 함수를 호출할 수 있는 것은 당연하다. 또한 << 연산자가 객체의 레퍼런스를 리턴하므로 cout으로 hex를 보낸 후 곧바로 정수를 출력하는 것도 가능하다. 예제의 실행 결과는 다음과 같다.

 

4d2

8진수 : 2322

16진수 : 4d2

10진수 : 1234

 

정수 1234가 16진수로 출력되었으며 << 연산자로 함수 포인터를 보내도 진법이 변경되는 것을 볼 수 있다. 진법 지정은 정수형에 대해서만 동작하며 실수나 문자열에 대해서는 아무런 영향도 주지 않는다. 다음은 출력폭과 관계된 조정자에 대해 연구해 보자.

 

: coutwidth

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     int i=1234;

     int j=-567;

 

     // 출력폭 지정

     cout << i << endl;

     cout.width(10);

     cout << i << endl;

     cout.width(2);

     cout << i << endl;

 

     // 채움 문자 지정

     cout.width(10);

     cout.fill('_');

     cout << i << endl;

     cout.fill(' ');

 

     // 정렬 지정

     cout.width(20);

     cout << left << j << endl;

     cout.width(20);

     cout << right << j << endl;

     cout.width(20);

     cout << internal << j << endl;

}

 

실행 결과는 다음과 같다.

 

1234

      1234

1234

______1234

-567

                -567

-                567

 

출력폭은 width 멤버 함수로 지정하는데 printf의 %10d랑 동일한 기능이다. 별다른 지정이 없을 경우 데이터의 원래 자리수만큼 출력하는데 정수 1234는 4칸, 문자열 "String"은 여섯 칸에 출력될 것이다. width 함수로 출력폭을 지정하면 최소한 지정한 폭만큼은 사용한다. 예제에서 i는 4자리수이므로 4칸에 맞추어 출력되지만 width(10)을 호출한 후 출력하면 10칸을 차지하고 앞쪽 여섯 칸이 공백으로 남겨진다.

, width가 지정하는 출력폭은 어디까지나 최소폭일 뿐이며 데이터의 길이가 최소폭보다 더 클 경우는 최소폭을 무시하고 데이터의 길이만큼 출력한다. width(2)로 최소폭을 지정한 후 i를 출력하면 4자리 모두 출력된다. width는 이어지는 출력에 대해 딱 한 번만 적용되며 출력 후 원래 설정으로 돌아오도록 되어 있다. 따라서 매 출력할 때마다 원하는 폭을 일일이 지정해야 한다. width 함수는 여러 가지 면에서 printf의 출력폭 지정 서식과 유사한 면이 많다.

fill 함수는 채움 문자를 지정하는데 출력폭이 데이터의 폭보다 더 넓을 경우 빈 칸을 어떤 문자로 채울 것인가를 지정한다. 디폴트는 공백 문자로 되어 있어 남는 폭은 빈 공백이 출력되지만 원할 경우 공백이 아닌 다른 문자를 출력하도록 할 수 있다. 예를 들어 화폐 액수같은 경우 출력된 결과의 앞뒤에 숫자를 더 적지 못하도록 하기 위해 공백이 아닌 다른 문자로 채워 넣기도 한다. 채움 문자는 한 번 지정하면 계속 유효하므로 채움 문자를 해제하려면 다시 공백 문자를 지정해야 한다.

정렬 지정은 출력폭이 데이터폭보다 더 클 경우 어느쪽에 데이터를 출력할 것인가를 지정하는데 left, right 조정자를 cout으로 보냄으로써 변경할 수 있다. internal은 다소 특수한 정렬인데 부호나 진법 지정(0x 등)은 왼쪽에, 숫자는 오른쪽에 출력하는 정렬 방식이다. 다음은 실수의 출력에 관련된 조정자에 대해 알아보자.

 

: coutfloat

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     double d=1.234;

 

     // 실수의 정밀도 지정

     cout << d << endl;

     cout.precision(3);

     cout << d << endl;

     cout.precision(10);

     cout << showpoint << d << endl;

     cout.precision(6);

 

     // 실수 출력 방식

     cout << fixed << d << endl;

     cout << scientific << d << endl;

}

 

실행 결과는 다음과 같다.

 

1.234

1.23

1.234000000

1.234000

1.234000e+000

 

cout은 디폴트로 실수를 여섯 자리까지만 출력하는데 precision 함수를 사용하면 정밀도를 조정할 수 있다. precision은 실수의 전체 출력 자리 수를 지정하는데 만약 지정한 자리수보다 더 길다면 뒤쪽의 수는 반올림 처리된다. 지정한 자리수보다 실수가 짧을 경우는 후행 제로를 출력하지 않는데 후행 제로까지 정확하게 출력하고 싶다면 showpoint라는 조정자를 사용한다.

실수의 출력 방식은 고정 소수점 방식과 부동 소수점 방식이 있는데 각각 fixed, scientific이라는 조정자로 변경할 수 있다. 다음은 나머지 잡다한 지정자들이다.

 

: coutmanip

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     int i=1234;

     double d=56.789;

     char *str="String";

     bool b=true;

 

     // bool형 출력 방식

     cout << b << endl;

     cout << boolalpha << b << endl;

 

     // 진법 접두 출력 및 대소문자

     cout << hex << i << endl;

     cout << showbase << i << endl;

     cout << uppercase << i << endl;

 

     // + 양수 기호 표시

     cout << dec << showpos << i << endl;

}

 

소스 내의 주석과 실행 결과를 보면 각 조정자의 역할을 쉽게 이해할 수 있을 것이다.

 

1

true

4d2

0x4d2

0X4D2

+1234

 

boolalpha 조정자는 bool형의 변수를 1 또는 0으로 출력하지 않고 true, false라는 문자열로 출력한다. showbase 조정자는 16진 표기시 0x라는 진법 표기를 붙이며 uppercase는 16진 표기에 사용되는 X와 A~F까지의 영문자를 대문자로 출력한다. showpos는 양수에 대해서도 + 부호를 출력한다.

출력 객체는 폭이나 정밀도, 정렬 방식, 채움 문자, 진법 등에 대한 옵션을 기억하는 플래그들을 가지며 조정자들은 이 플래그를 변경하는 역할을 한다. 조정자 외에도 플래그를 직접 변경하는 setf, unsetf라는 멤버 함수들이 있는데 이 함수들을 사용하면 출력 양식을 일괄적으로 한꺼번에 변경하거나 조사할 수도 있다.