본문 바로가기
프로그래밍/C & C++

[C++] 스트림(stream) 클래스 (2)

by 별준 2022. 12. 15.

References

  • C++ Standard Library 2nd

Contents

  • Standard Input/Output Functions, sentry 객체
  • Manipulators (조작자)
  • Formatting (포맷)

[C++] 스트림(stream) 클래스 (1)

 

[C++] 스트림(stream) 클래스 (1)

References C++ Standard Library 2nd Contents Background of I/O Streams Fundamental Stream Classes and Objects Standard Stream Operators > State of Streams I/O 형식을 위한 클래스는 C++ 표준 라이브러리에서 가장 중요한 부분이며, I/O

junstar92.tistory.com

지난 포스팅에 이어 스트림 클래스에 대해서 살펴보도록 하겠습니다.

 

Standard Input/Output Functions

스트림의 표준 연산자(<<, >> 연산자)를 사용하는 대신, 멤버 함수를 통해 읽기 연산과 쓰기 연산을 수행할 수 있습니다. 이 함수들은 << 연산자나 >> 연산자와 달리 unformatted data를 읽거나 씁니다. 또한 읽을 때 앞서 나오는 공백(whitespace)를 스킵하지도 않습니다. 이 기능은 sentry 객체로 처리되며, 이들 함수는 formatted I/O 연산자와는 다른 방식으로 예외를 처리합니다. 만약 예외가 발생하면, 호출된 함수나 상태 플래그를 set한 결과이건 간에 badbit 플래그가 set 됩니다. 그러면 exception mask에 badbit가 set되어 있다면 예외를 다시 던집니다.

 

표준 I/O 함수는 <ios>에 정의된 데이터 타입인 streamsize를 사용하여 counts를 명시합니다.

namespace std {
  typedef ... streamsize;
  ...
}

streamsize 타입은 항상 size_t의 부호가 있는 버전입니다 (음수값도 나타내야 하기 때문).

 

Member Functions for Input

istream은 read를 위해 사용되는 스트림 클래스를 나타냅니다.

istream의 경우(for char 타입), 문자 시퀀스를 읽기 위한 여러 가지 멤버 함수를 제공하는데, cppreference(link)에 있는 멤버 함수는 다음과 같습니다. 

간단하게 istream(basic_istream<char>)에서 제공되는 멤버 함수들에 대해서 살펴보도록 하겠습니다.

 

int istream::get()

  • 다음 문자 하나를 읽으며, 읽은 문자나 EOF를 반환합니다.
  • 일반적으로 반환 타입은 traits::int_type 이며, EOF인 경우 반환값은 traits::eof() 입니다. istream의 경우 반환 타입이 int_type으로 표시되어 있는데 실제 타입은 int 입니다(char_traits<char>::int_type으로 결정). 따라서, istream일 경우, 이 함수에 대응되는 C 함수는 getchar()나 getc()에 해당합니다.

istream& istream::get(char& c)

  • 다음 문자 하나를 전달된 인자 c에 할당합니다.
  • 스트림을 반환하는데, 성공 여부를 스트림의 상태로 알 수 있습니다.

istream& istream::get(char* str, streamsize count)
istream& istream::get(char* str, streamsize count, char delim)

  • 두 함수 모두 최대 count-1개의 문자를 읽어 str가 가리키는 문자 시퀀스에 저장합니다.
  • 첫 번째 함수는 다음에 읽을 문자가 개행 문자이면 읽기를 중단하는데, istream인 경우 '\n', wistream인 경우 wchar_t('\n')가 여기에 해당합니다.
  • 두 번째 함수는 다음에 읽을 문자가 delim이라면 읽기를 중단합니다.
  • 읽은 문자 시퀀스는 NULL로 끝납니다.

istream& istream::getline(char* str, streamsize count)
istream& istream::getline(char* str, streamsize count, char delim)

  • 두 함수는 각각 위에서 살펴본 get()의 두 함수와 유사하지만, 다음과 같은 차이점이 있습니다.
    - 각각 개행 문자나 delim을 포함해서 읽습니다. 따라서, count-1개의 문자 이전에 개행 문자나 delim이 있다면 이들도 포함하여 읽습니다. 하지만 str에 저장하지는 않습니다.
    - 만약 count-1개의 문자보다 긴 행을 읽는다면 failbit를 설정합니다.

istream& istream::read(char* str, streamsize count)

  • count개의 문자를 읽어 str에 저장합니다.
  • str 문자열은 NULL 문자로 끝나지 않습니다.

streamsize istream::readsome(char* str, streamsize count)

  • 최대 count개의 문자를 읽어 문자열 str에 저장하고, 읽은 문자의 수를 반환합니다.
  • str 문자열은 NULL 문자로 끝나지 않습니다.
  • read()와 반대로 readsome()은 버퍼의 멤버 함수인 in_avail()을 사용하여 스트림 버퍼 내에서 가져올 수 있는 모든 문자를 읽습니다. 입력이 키보드나 다른 프로세스에서 오기 때문에 입력을 기다리고 싶지 않다면 이 함수가 유용합니다. 파일의 끝을 만나더라도 에러로 인식하지 않습니다.

streamsize istream::gcount() const

  • 마지막 unformatted read 연산에서 읽은 문자의 수를 반환합니다.

istream& istream::ignore()

istream& istream::ignore(streamsize count)

istream& istream::ignore(streamsize count, int delim)

  • ignore 함수는 모두 문자를 추출한 뒤 버립니다.
  • 첫 번째 함수는 하나의 문자만 무시하며, 두 번째 함수는 최대 count개의 문자를 무시합니다. 마지막 함수는 delim을 추출할 때까지 최대 count개의 문자를 무시합니다.
  • 만약 count가 std::numeric_limits<std::streamsize>::max() 라면 delim이나 파일의 끝에 도달할 때까지 모든 문자를 버립니다.

int istream::peek()

  • 스트림에서 다음 문자를 읽지만 추출(소비)하지는 않습니다. 이 함수를 호출 이후 읽을 때에도 읽는 위치가 변경되지 않으면 이 문자부터 시작하게 됩니다.
  • 더 이상 읽을 문자가 없다면 EOF를 반환합니다.

istream& istream::unget()

istream& istream::putback(char c)

  • 두 함수 모두 마지막으로 읽은 문자를 스트림으로 되돌려 놓고 다음 번 읽기에서 다시 읽을 수 있도록 합니다.
  • putback()은 인자로 전달된 문자 c가 마지막으로 읽은 문자인지 확인하는데, 잘못된 문자를 되돌려놓으려고 할 때 badbit가 set 되어 예외가 발생할 수 있습니다.
  • 문자를 되돌릴 수 없다면 마찬가지로 badbit가 set 됩니다.

 

C-string을 읽을 때에는 >> 연산자보다 위의 함수들을 사용하는 것이 읽을 수 있는 최대 문자 수를 인자로 전달하기 때문에 더 안전합니다. >> 연산자를 사용하더라도 읽을 문자의 수를 제한할 수는 있지만, 위의 함수가 더 기억하기가 쉬운 편 입니다.

 

istream 멤버 함수를 사용하는 것보다 스트림 버퍼를 직접 사용하는 것이 더 나을 때가 있는데, 스트림 버퍼는 한 문자나 문자 순열을 효율적으로 읽는 멤버 함수를 제공합니다. 밑에서 살펴보겠지만 sentry 객체를 생성하는 load도 없습니다. 후반부에서는 스트림 버퍼 인터페이스를 사용하는 방법에 대해서 살펴 볼 예정이며, 이를 사용하면 클래스 템플릿 istreambuf_iterator<>를 사용하여 스트림 버퍼에 대한 반복자를 사용할 수 있습니다.

 

추가로 읽는 위치를 조작하는 tellg()와 seekg()가 있는데 이 멤버 함수는 주로 파일에서 사용되며, 파일 스트림에 대해 살펴 볼 때 언급하도록 하겠습니다.

 

Member Functions for Output

ostream은 쓰기를 위한 스트림 클래스를 나타냅니다.

ostream& ostream::put(char c)

  • 인자로 전달된 문자 c를 스트림에 write 합니다.
  • 스트림을 반환하는데, 성공 여부를 스트림의 상태를 통해 알 수 있습니다.

ostream& ostream::write(const char* str, streamsize count)

  • 문자열 str에서 count개의 문자를 스트림에 write 합니다.
  • NULL 문자가 있더라도 write를 중단하지 않으며 스트림에 write 합니다.

ostream& ostream::flush()

  • 스트림의 버퍼를 비워내고, 버퍼된 데이터를 스트림이 속한 장치나 I/O에 강제로 write 합니다.

이외에 write 위치를 조작하는데 tellg()와 seekg()가 있는데, 주로 파일에서 사용되며 파일 스트림에 대해서 살펴 볼 때 알아보겠습니다.

입력 함수들과 마찬가지로 스트림 버퍼를 직접 사용하거나 unformatted write 연산을 지원하는 클래스 템플릿인 ostreambuf_iterator<>를 사용하는 것도 좋습니다. 사실 unformatted output 함수들은 sentry 객체를 사용한다는 점만 제외하고는 크게 더 나은 부분이 없습니다. sentry 객체가 하는 역할로는 output stream의 동기화가 대표적입니다.

 

Example

단순히 읽은 문자를 모두 출력하는 전형적인 코드를 C++로 구현하면 다음과 같습니다.

#include <iostream>

using namespace std;

int main()
{
  char c;

  // while it is possible to read a character
  while (cin.get(c)) {
    // print it
    cout.put(c);
  }
}

더 성능을 높이기 위해서 스트림 버퍼를 직접 조작할 수도 있습니다. 위의 코드를 스트림 버퍼 반복자를 사용하거나 하나의 명령문으로 전체 입력을 다 복사하는 예제 코드는 후반부에서 따로 살펴 볼 수 있습니다.

 

sentry 객체

I/O 스트림 연산자와 함수는 이들의 기능을 제공하기 위해서 common scheme를 사용합니다. 먼저, 전처리를 통해 I/O를 위한 스트림을 준비하고, 그 다음에 실제 I/O를 수행하고 후처리를 진행합니다.

 

이러한 기법을 구현하기 위해서 basic_istream과 basic_ostream은 보조 클래스인 sentry를 정의합니다. 이 클래스의 생성자가 전처리를 담당하고, 소멸자는 후처리를 담당합니다. 따라서 모든 formatted/unformatted I/O 연산자와 함수는 다음과 같이 실제 작업 및 연산을 실행하기 전에 sentry 객체를 사용합니다.

sentry se(strm); // indirect pre- and postprocessing
if (se) {
  .. // the actual processing
}

sentry 객체는 전/후처리를 해야하는 스트림을 인자로 받습니다. 그 후 나머지 작업은 스트림이 good 상태에 있는지를 알려주는 스트림의 상태에 따라 진행됩니다. Input 스트림인 경우, sentry 객체는 optional인 불리언 값을 전달하여 skipws 플래그가 set 되어 있더라도 공백을 무시하지 않도록 알릴 수 있습니다.

sentry se(strm, true); // don't skip whitespaces during the additional processing

 

전/후처리는 스트림을 사용하는 I/O의 모든 일반적인 작업을 수행합니다. 여기에는 다양한 스트림을 동기화시키고, 스트림의 상태를 검사하고, 공백을 건너뛰는 등 구현에 따라 달라질 수 있는 모든 작업이 포함됩니다. 예를 들어, 멀티 스레드 환경에서는 lock과 같은 부가적인 처리가 필요할 수 있습니다.

 

만약 I/O 연산자가 스트림 버퍼를 직접 처리한다면 이에 대응되는 sentry 객체를 먼저 생성해야 합니다.

 

Manipulators

<istream>과 <ostream>에 정의된 기본적인 조작자들은 다음과 같습니다.

아마 위의 조작자들은 익숙하실 거라 생각됩니다.

 

일부 조작자들은 인자를 받아서 처리합니다. 예를 들어, 아래의 명령문을 통해 다음에 출력할 문자열의 최소 너비와 너비를 채우지 못했을 때 나머지 공간에 채울 문자를 설정할 수 있습니다.

std::cout << std::setw(6) << std::setfill('_');

인자를 받는 표준 조작자들은 <iomanip>에 정의되어 있습니다.

 

Overview of All Manipulators

표준 라이브러리에서 제공하는 조작자들은 위와 같습니다. C++11 기준이기 때문에 현재는 이외에 추가된 것들도 있을 수 있습니다.

 

How Manipulators Work

조작자는 스트림을 쉽게 조작하게 해줄 뿐 아니라 함수 오버로딩의 강력함을 이용하여 매우 간단한 트릭을 사용하여 구현되었습니다. 실제 조작자는 I/O 연산자에 인자로 전달된 함수 이상도 이하도 아닙니다. 이 함수들은 연산자에 의해 호출되는데, 예를 들어, ostream 클래스를 위한 출력 연산자는 기본적으로 아래와 같이 오버로딩됩니다.

ostream& ostream::operator<<(ostream& (*op)(ostream&))
{
  // call the function passed as parameter with this stream as the argument
  return (*op)(*this);
}

op는 ostream을 인자로 받고 ostream을 반환하는 함수 포인터입니다. 만약 << 연산자의 두 번째 피연산자가 이 타입의 함수 포인터라면 이 함수는 << 연산자의 첫 번째 피연산자를 인자로 전달하여 호출됩니다.

예를 들어, endl()은 기본적으로 아래와 같이 구현됩니다.

이 조작자는 표현식에서 아래와 같은 방식으로 쓸 수 있습니다.

std::cout << std::endl;

<< 연산자는 스트림 cout에 대해 endl()을 두 번째 인자로 전달하여 호출합니다. << 연산자는 이 호출을 전달된 함수에 스트림을 인자로 전달하는 호출로 변환합니다.

std::endl(std::cout);

위 표현식을 직접 작성하여 호출해도 조작자를 사용하는 효과를 얻을 수 있습니다.
또한, 함수 표기법을 사용하면 ADL(argument-dependent lookup)에 따라 다음과 같이 조작자에 네임스페이스를 제공하지 않아도 되어 편리할 수 있습니다.

endl(std::cout);

 

User-Defined Manipulators

사용자 정의 조작자를 정의하고 싶다면 endl()과 같은 함수를 만들기만 하면 됩니다. 예를 들어, 아래 코드는 행의 끝까지 모든 문자를 무시하도록 하는 조작자를 정의합니다.

#include <istream>
#include <limits>

template<typename charT, typename traits>
inline
std::basic_istream<charT, traits>&
ignoreLine(std::basic_istream<charT, traits>& strm)
{
  // skip until end-of-line
  strm.ignore(std::numeric_limits<std::streamsize>::max(), strm.widen('\n'));
  // return stream for concatenation
  return strm;
}

이렇게 정의한 조작자를 사용하는 방법도 매우 간단합니다.

std::cin >> ignoreLine;

이를 여러 번 사용하면 여러 행을 스킵할 수 있습니다.

std::cin >> ignoreLine >> ignoreLine;

 

인자를 받는 사용자 정의 조작자를 정의하는 데에는 여러 가지 방법이 있는데, 예를 들어, n개의 행을 무시하는 조작자는 다음과 같이 정의할 수도 있습니다.

class ignoreLine
{
private:
  int num;
public:
  explicit ignoreLine(int n = 1) : num(n) {}
  
  template<typename charT, typename traits>
  friend std::basic_istream<charT, traits>&
  operator>>(std::basic_istream<charT, traits>& strm, const ignoreLine& ign)
  {
    // skip until end-of-line num times
    for (int i = 0; i < ign.num; ++i) {
      strm.ignore(std::numeric_limits<std::streamsize>::max(), strm.widen('\n'));
    }
    // return stream for concatenation
    return strm;
  }
};

여기서 조작자인 ignoreLine은 초기화 인자를 받는 클래스이며, 입력 연산자는 이 클래스의 객체로 오버로딩됩니다.

 

 

Formatting

숫자의 정밀도나 숫자의 진수, 또는 채움 문자(fill character)가 I/O 포맷의 정의에 영향을 줍니다. 이번에는 format flags에 대해서 살펴보도록 하겠습니다.

 

Format Flags

ios_base 클래스와 basic_ios<> 클래스는 다양한 I/O 포맷을 정의하기 위해 사용되는 여러 가지 멤버를 갖습니다. 예를 들어, 일부 멤버는 minimum width나 부동소수점의 정밀도 또는 fill character를 저장합니다. 타입이 ios::fmtflags인 멤버는 configuration 플래그를 저장하는데, 예를 들어, 양수 앞에 +를 붙이는지, 불리언 값을 숫자로 출력할 것인지 문자열로 출력할 것인지 등을 정의합니다.

 

일부 플래그들은 그룹을 이루는데, 예를 들어, 8진수, 10진수, 또는 16진수로 표현할 것인지 나타내는 플래그는 그룹을 이룹니다. 이런 그룹들을 쉽게 처리하기 위해 특별한 마스크가 정의되어 있습니다.

스트림의 모든 format 정의를 다루기 위해 위와 같은 멤버 함수들을 사용할 수 있습니다. setf()와 unsetf() 함수는 하나 혹은 그 이상의 플래그를 set/clear 합니다. '|(OR)' 연산자를 사용하여 여러 플래그를 한 번에 조작할 수도 있습니다. setf() 함수가 두 번째 인자를 받으면 첫 번째 인자로 받은 플래그들을 set하기 전에 두 번째 인자로 받은 마스크에 따라 그룹 내 모든 플래그를 clear 합니다. 이때 모든 작업은 그룹 내 플래그로 한정됩니다. 아래 예제 코드를 참조바랍니다.

// set flags showpos and uppercase
std::cout.setf(std::ios::showpos | std::ios::uppercase);

// set only the flag hex in the group basefield
std::cout.setf(std::ios::hex, std::ios::basefield);

// clear the flag uppercase
std::cout.unsetf(std::ios::uppercase);

 

flags()를 사용하면 모든 포맷 플래그를 한 번에 조작할 수 있으며, 인자 없이 flags()를 호출하면 현재 포맷 플래그를 반환합니다. 현재 플래그 상태를 저장한 이후 원래 상태로 복구하려고 할 때 flags()를 사용하면 유용합니다.

using std::ios;
using std::cout;

// save current format flags
ios::fmtflags oldFlags = cout.flags();

// do some changes
cout.setf(ios::showpos | ios::showbase | ios::uppercase);
cout.setf(ios::internal, ios::adjustfield);
cout << std::hex << x << std::endl;

// restore saved format flags
cout.flags(oldFlags);

 

copyfmt()를 사용하면, 한 스트림에서 다른 스트림으로 모든 포맷 정보를 복사할 수 있습니다.

 

포맷 플래그를 set/clear할 때 아래의 조작자를 사용할 수도 있습니다. 이를 사용하려면 <iomanip>을 include 해야 합니다.

#include <iostream>
#include <iomanip>
...
std::cout << resetioflags(std::ios::adjustfield) // clear adjustment flags
          << setiosflags(std::ios::left); // left-adjust values

 

Boolean Value Format

boolalph 플래그는 불리언 값을 읽거나 쓸 때 사용할 포맷을 정의합니다. 이 플래그는 set되었다면 불러인 값을 글자로 표현하고, clear되었다면 숫자로 표현합니다.

 

만약 플래그가 set 되지 않았다면(default), 불리언 값은 숫자 문자열로 표현됩니다. 이 경우, '0'은 항상 false를 나타내고 '1'은 항상 true를 나타냅니다. 이외의 값은 에러로 간주됩니다.

 

이 플래그를 편리하게 조작하기 위해서 boolalpha, noboolalpha 조작자가 정의되어 있으며 다음과 같이 사용할 수 있습니다.

bool b = true;
std::cout << std::noboolalpha << b << " == "
          << std::boolalpha << b << std::endl;

 

Field With, Fill Character, and Adjustment

너비와 채움 문자를 정의하기 위해 width()와 fill() 이라는 두 개의 멤버 함수가 사용됩니다.

 

width()는 출력의 최소 너비를 결정하며, 다음에 출력될 formatted field를 쓸 때에만 적용됩니다. 인자없이 width()를 호출하면 현재 field width를 반환합니다. 그리고 숫자를 인자로 전달하여 호출하면 width를 변경하고, 이전 값을 반환합니다. 최소 너비가 지정되더라도 너비를 넘어서는 부분의 출력을 생략하지는 않습니다. 따라서 최대 너비는 지정할 수 없으며, 이런 기능을 원한다면 직접 구현해야 합니다.

fill()은 출력할 formatted representation 값과 최소 너비 사이를 채울 때 사용할 fill character를 정의하며, 기본값은 공백입니다.

 

필드 내에서 값을 조정하고 싶다면, 다음의 세 가지 플래그를 사용하면 됩니다.

위 플래그들은 ios_base 클래스에서 대응되는 마스크와 함께 정의되어 있습니다.

아래 표는 위에서 설명한 것들을 다양하게 적용한 예시를 보여줍니다.

formatted I/O 연산자는 수행되고 난 후에는 기본 너비 값으로 복원되는데, fill character와 adjustment는 명시적으로 다시 복원하기 전까지 바뀌지 않습니다.

 

width나 fill character를 조금 더 쉽게 사용할 수 있도록 이를 위한 다양한 조작자가 정의되어 있습니다.

setw()와 setfill()은 <iomanip> 헤더를 include해야 합니다. 예를 들어, 다음과 같이 사용할 수 있습니다.

#include <iostream>
#include <iomanip>

int main()
{
  std::cout << std::setw(8) << std::setfill('_') << -3.14
            << ' ' << 42 << std::endl;
  std::cout << std::setw(8) << "sum: "
            << std::setw(8) << 42 << std::endl;
}

위 코드를 실행하면 다음과 같이 출력됩니다.

 

char* 타입의 문자 시퀀스를 읽을 때, 읽을 문자의 최대 수를 정의하기 위해서 width를 사용할 수도 있습니다. 만약 width()가 0이 아니라면 최대 width() - 1개의 문자를 읽습니다. 일반적인 C-string은 값을 읽는 동안 커질 수 없기 때문에 width() 또는 setw()를 항상 >> 연산자와 함께 사용해야 합니다. 예를 들어,

char buffer[81];
// read, at most, 80 characters
cin >> setw(sizeof(buffer)) >> buffer;

위와 같이 사용합니다. 위 코드는 최대 80개의 문자를 읽습니다. sizeof(buffer)의 크기는 81이지만 자동으로 추가되는 NULL 문자를 위해 하나를 사용합니다.

하지만, string을 사용하는 것이 에러도 적고 편리합니다.

string buffer;
cin >> buffer;

 

Positive Sign and Uppercase Letters

숫자값의 출력에 영향을 주는 포맷 플래그로는 showpos와 uppercase가 있습니다.

ios::showpos를 사용하면 양수 값을 출력할 때 부호를 붙일 것인지 여부를 결정할 수 있습니다. 만약 플래그가 set 되지 않았다면 음수 값에만 부호를 붙입니다.

ios::uppercase를 사용하면 숫자값에서 문자가 있는 경우 대문자로 표시할 것인지 결정합니다. 이 플래그는 16진수 포맷으로 표현된 정수와 scientific notation으로 표시된 부동소수점 값에 적용됩니다. 기본적으로는 소문자로 표시되며 + 기호는 붙이지 않습니다.

#include <iostream>

int main()
{
  std::cout << 12345678.9 << std::endl;
  std::cout.setf(std::ios::showpos | std::ios::uppercase);
  std::cout << 12345678.9 << std::endl;
}

위의 두 플래그 역시 조작자를 통해서 설정하거나 삭제할 수도 있습니다.

 

Numeric Base

정수값 I/O에 어떤 진수(base)를 사용할 지 결정하는데 사용되는 플래그로는 3가지가 있습니다. 이 플래그는 다음과 같으며 ios_base에 정의되어 있습니다.

진수를 변경하면 플래그가 다시 설정될 때까지 출력되는 모든 정수에 영향을 미치며, 기본적으로 10진수가 사용됩니다. 2진수는 지원되지 않습니다. 단, bitset 클래스를 사용하여 정수값을 2진수로 읽고 쓸 수 있습니다.

 

진수를 위한 플래그는 입력에도 영향을 주는데, 숫자 진수를 위한 플래그 중 하나가 set되었다면 모든 숫자는 해당 기수를 사용합니다. 만약 플래그가 설정되지 않았다면, 앞서 나오는 숫자에 따라 진수가 결정됩니다. 만약 0x 또는 0X로 시작하는 숫자라면 16진수로 읽습니다. 0으로 시작하는 숫자는 8진수로 간주합니다. 그 이외에는 10진수로 간주합니다.

 

이 플래그를 수정하는 방법에는 다음의 두 가지 방법이 있습니다.

- 한 플래그를 삭제한 후 다른 플래그를 설정

std::cout.unsetf(std::ios::dec);
std::cout.setf(std::ios::hex);

- 한 플래그를 설정하고 그룹 내 나머지 플래그는 모두 자동으로 삭제

std::cout.setf(std::ios::hex, std::ios::basefield);

 

마찬가지로 이를 쉽게 사용할 수 있도록 조작자도 제공합니다.

 

또 다른 플래그인 showbase를 사용하면 C와 C++에서 일반적으로 진수를 표현하는 글자를 추가로 출력합니다.

#include <iostream>

using namespace std;

int main()
{
  cout << 127 << ' ' << 255 << endl;
  cout << hex << 127 << ' ' << 255 << endl;

  cout.setf(std::ios::showbase);
  cout << 127 << ' ' << 255 << endl;

  cout.setf(std::ios::uppercase);
  cout << 127 << ' ' << 255 << endl;  
}

위 코드의 출력은 다음과 같습니다.

 

Floating-Point Notation

부동소수점 값을 출력할 때 사용되는 플래그와 멤버는 다양합니다.

이들 플래그는 ios_base에 정의되어 있으며, 만약 ios::fixed가 set 되어 있다면 부동소수점 값은 10진수로 출력됩니다. 만약 ios::scientific이 set 된다면 scientific notation(exponential)로 출력됩니다.

 

플래그인 showpoint를 사용하면 스트림에 소수점을 찍고, 현재 정밀도에 맞춰 0을 나열하도록 만들 수 있습니다.

정밀도를 정의할 때는 멤버 함수인 precision()을 사용할 수 있습니다.

 

위의 기능들도 편리하게 사용할 수 있도록 조작자를 제공합니다.

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
  cout << scientific << showpoint
       << setprecision(8)
       << 0.123456789 << endl;
}

setprecision()은 <iomanip>을 include 해야 사용할 수 있습니다.

 

General Formatting Definitions

위에서 설명한 플래그 이외에 skipws와 unitbuf 플래그가 있습니다.

기본적으로 ios::skipws가 set 되어 있으면 >> 연산자는 앞서 나오는 공백을 스킵합니다. 대체로 이 플래그가 set 되어 있는 편이 편리한데, 이 플래그가 설정되어 있다면 숫자 사이를 분리하기 위해 삽입된 공백을 명시적으로 읽지 않게 되어 편리하게 공백으로 분리된 숫자들을 각각 따로 읽을 수 있습니다. 하지만 >> 연산자를 통해 공백을 읽을 수 없다는 뜻이기도 합니다.

 

플래그 ios::unitbuf를 사용하면, 출력의 버퍼 방식을 제어할 수 있습니다. 이 플래그가 set 되어 있다면 output은 버퍼에 쌓이지 않고 매 쓰기 연산마다 output buffer가 완전히 flush 됩니다. 기본적으로 이 플래그는 거의 모든 스트림에서 clear 되어 있지만, cerr와 wcerr의 경우 초기값으로 이 플래그가 set 되어 있습니다.

 

위의 플래그도 마찬가지로 조작자로 조작할 수 있습니다.

 

댓글