[ C++ ] C++의 표준 입출력

개발하는 곰댕이·2022년 5월 29일
0

cpp

목록 보기
11/11

버퍼와 스트림

스트림

C++ 프로그램은 입력과 출력을 바이트들의 흐름(스트림)이라고 부릅니다.
이런 스트림은 프로그램과 파일, 혹은 프로그램과 다른 프로그램 등 시작점과 목적지 사이의 통로 역할을 해 줍니다.
간단하게 말 하면 C++의 입력과 출력 시스템 덕분에 우리는 파일로부터 들어오는 입력과 키보드로부터 들어오는 입력을 별도의 특별한 처리 없이 같은 방법으로 처리할 수 있게 된 것입니다.

버퍼

입력과 출력은 효율적인 처리를 위해 버퍼를 사용합니다.
이 버퍼의 용도는 입력과 출력의 데이터들을 하나하나 전송하지 않고 버퍼에 모아뒀다가 한번에 전송하기 위해서 존재하는 임시 저장 장치입니다.

그러면 이 버퍼는 왜 써야할까요?

일반적으로 디스크 드라이브 장치들은 512바이트 이상의 블록단위로 정보를 전송할 수 있습니다. 하지만 우리가 만든 프로그램들은 1바이트씩 처리를 하게 되죠.
디스크에 접근해서 하나하나 읽고 쓰기엔 너무 많은 파워가 소모됩니다. 그렇기 때문에 최대한 쌓아둘 수 있을 만큼 쌓아뒀다가 한번에 처리하는 거죠.

파일의 입력을 예로 들면 파일의 데이터를 읽어와서 버퍼에 채웁니다. 그리고 우리가 만든 프로그램은 그 버퍼를 읽어서 데이터를 처리하고, 버퍼가 다 비워졌으면 다시 입력을 받으며 동작하게 됩니다.
마찬가지로 파일로의 출력도 버퍼를 우선 채우고 나서 출력을 하며 출력 버퍼를 비워줍니다.

그럼 키보드의 입력은 어떨까요?
키보드는 문자를 한 번에 하나씩 발생시키기 때문에 굳이 버퍼를 사용할 필요가 없지만 버퍼를 사용한 입력같은 경우에는 대부분 사용자가 엔터를 누를 때 입력버퍼를 비우게 됩니다.

버퍼와 스트림 관리

이런 버퍼와 스트림의 관리를 쉽게 해주는 클래스가 몇 가지 있습니다.

  • streambuf
    streambuf 클래스는 버퍼로 사용할 메모리를 제공하고, 버퍼를 채우고, 접근하고, 비우고, 메모리를 관리하는 등 버퍼에 관련된 메소드를 제공합니다.
  • ios_base
    ios_base 클래스는 모든 입출력 스트림의 base클래스이며 스트림의 타입이 2진인지, 텍스트인지 혹은 열려있는지 등 스트림의 일반적인 특성 및 상태를 알려주는 클래스입니다.
  • ios
    ios 클래스는 ios_base에서 파생된 클래스이며 streambuf 객체를 가리키는 포인터 멤버를 갖고 있습니다.
  • ostream
    ostream클래스는 ios클래스에서 파생된 클래스이며 출력 메소드를 담당합니다.
  • istream
    istream클래스는 ios클래스에서 파생된 클래스이며 입력 메소드를 담당합니다.
  • iostream
    iostream 클래스는 ostream, istream을 둘 다 상속받기 때문에 입력과 출력을 모두 다루며 우리가 흔히 사용하는 cin, cout이 포함되어 있습니다.
    이 iostream을 include하면 다음과 같은 8개의 스트림 객체가 포함됩니다.
     extern _LIBCPP_FUNC_VIS istream cin;   // 표준 입력 스트림 입니다.
     extern _LIBCPP_FUNC_VIS wistream wcin; // wchar_t 전용 입력 스트림 입니다.
     extern _LIBCPP_FUNC_VIS ostream cout;  // 표준 출력 스트림 입니다.
     extern _LIBCPP_FUNC_VIS wostream wcout;// wchar_t 전용 출력 스트림 입니다.
     extern _LIBCPP_FUNC_VIS ostream cerr;  // 표준 에러 스트림 입니다.
     extern _LIBCPP_FUNC_VIS wostream wcerr;// wchar_t 전용 에러 스트림 입니다.
     extern _LIBCPP_FUNC_VIS ostream clog;  // 표준 에러 스트림 입니다.
     extern _LIBCPP_FUNC_VIS wostream wclog;// wchar_t 전용 에러 스트림 입니다.

C++의 기본 자료형인 char는 1바이트이기 때문에 이모티콘, 한국어 등 확장 문자는 다루지 못 합니다.
그 문제를 해결하기위해서 확장 문자형인 wchar_t가 나왔으며 C++11에서는 char16_t char_32_t가 추가되었습니다.
이 녀석들의 입력과 출력을 위한 클래스는 wistream과 wostream으로 기본 스트림에 w가 추가된 형태입니다.

표준 출력( ostream )

cout은 ostream 기반으로 만들어진 객체이며 이 ostream 클래스의 중요한 일 중 하나는 int나 float 같은 데이터를 문자들의 스트림으로 변경해주는 것입니다.

<< 연산자 (삽입 연산자)

우리가 가장 많이 사용해온 연산자입니다. 이 연산자는 C++의 모든 기본 자료형을 다룰 수 있도록 오버로딩되어 있으며 그 목록은 다음과 같습니다.

  • unsigned char
  • signed char
  • char
  • short
  • int
  • unsigned int
  • long
  • unsigned long
  • long long (C++11)
  • unsigned long long (C++ 11)
  • float
  • double
  • long double

포인터형의 자료형은 다음과 같습니다.

  • const signed char *
  • const unsigned char *
  • const char *
  • void *
int	main(void)
{
	const char*	str = "123";

	cout << str << " " << (void*)str << endl;
	return (0);
}

출력

123 0x10083bd60

<< 연산자의 반환형

basic_ostream& operator<<( )

함수의 프로토타입은 위와 마찬가지로 otream의 참조를 반환하고 있으며 이 반환값은 호출자이자 자기자신을 참조한 값입니다.

즉 cout을 통해 << 연산자를 호출했으니 cout 객체의 참조값이 반환되는 것 입니다.

그러면 왜 이렇게 만들어 놓은 걸까요?

당연하게도 연속된 출력을 위해서 입니다.

cout << "123" << "456" << endl;

다음과 같은 코드가 있을 때 cout << “123” 이 먼저 실행되어 cout 객체를 다시 반환합니다. 그러면 그 객체의 << 연산자를 다시 호출해 cout << “456”이 실행되고, cout << endl이 실행되는 것 입니다.

이런 식으로 연속된 호출을 위해서 자기 자신의 참조값을 반환하는 것 입니다.

출력버퍼 비우기

우선 cout을 통해서 표준 출력을 사용할 때 무슨 일이 일어나는지 알아봅시다.

우선 출력이 버퍼에 가득 찰 때까지 버퍼에 쌓이게 됩니다. 버퍼는 보통 512 혹은 512의 배수의 바이트를 가지며 출력이 모두 끝나면 버퍼를 비우게 됩니다.

하지만 화면 출력을 위해서 버퍼를 채우는 것은 좋지 않습니다. 그렇기 때문에 이 경우에는 개행을 만나게 되면 버퍼가 자동으로 비워지게 됩니다.

또한 다음과 같이 입력을 받아야 하는 상황에도 버퍼를 비우게 됩니다.

cout << "입력 : ";
int num;
cin >> num;

뿐만아니라 flush 조정자를 사용한다면 우리가 원하는 시기에 버퍼를 강제로 비울수도 있습니다.

cout << "Hello world!" << flush;

ostream의 출력 상태를 변경 및 확인하는 ios_base

ios_base에 있는 다양한 플래그 및 함수를 이용해서 출력을 제어할 수 있습니다.

예를들면 precision을 통해서 소숫점을 제어한다던지

cout.precision(3);

width를 통해서 폭을 조절하는 등의 작업이 가능합니다.

cout.width(2);

뿐만아니라 setf()함수를 이용해 다양하게 출력을 제어할 수 있습니다.

cout.setf(ios_base::showpoint);//소숫점이 출력하도록 만듬

setf에 대해

setf에 대해서 조금 더 설명하자면 setf의 원형은 다음과 같습니다.

fmtflags setf(fmtflags);

리턴값은 변경 전 fmtflags 값이고, 매개변수는 변경할 fmtflags 값입니다.

fmtflags는 출력의 형식을 보관하는 비트마스크입니다. 다양한 플래그를 이용해서 비트를 채우면 해당 비트에 따라 출력의 형식이 정해집니다.

플래그의미
ios_base::boolalphabool 값을 true와 false로 입력하고 출력합니다.
ios_base::noboolalphabool 값을 1과 0으로 입력하고 출력합니다.
ios_base::showpoint항상 소수점을 표시합니다.
ios_base::uppercase16진수 출력, E표기에 대문자를 사용합니다.

이 플래그들 이외에도 많은 값이 있으며 이 플래그를 조합해서 fmtflags를 만듭니다.

이렇게 변경된 출력의 형식은 형식이 다시 지정될 때까지 지속됩니다.

표준 입력 ( istream)

표준 입력의 cin은 istream을 기반으로 만들어 졌으며 istream의 역할은 표준 입력을 알맞은 자료형으로 변환해 줍니다.

>> 연산자 ( 추출 연산자 )

>> 연산자 또한 기본 자료형을 인식할 수 있게 오버로딩을 합니다.

  • unsigned char &
  • signed char &
  • char &
  • short &
  • unsigned short &
  • int &
  • unsigned int &
  • long &
  • unsigned long &
  • long long & (C++11)
  • unsigned long long & (C++ 11)
  • float &
  • double &
  • long double &

포인터는 다음과 같습니다.

  • signed char *
  • unsigned char *
  • char *

cin의 입력 방식

cin의 입력 방식은 처음에 들어온 화이트 스페이스를 모두 무시하고 나오는 최초의 문자부터 시작하여 우리가 입력한 자료형에 맞지 않는 데이터가 올 때까지 입력을 받습니다.

int num;
cin >> num;

--------------------------
123z123
--------------------------
cout << num;
--------------------------
123

위 내용처럼 int에 맞는 데이터만 가져오고 남은 데이터는 입력 버퍼에 남겨둡니다.

스트림의 상태 확인

이 스트림들 또한 잘못된 값이나 에러가 날 수 있습니다. 그걸 확인하기 위해 존재하는 것들이 있습니다.

일단 기본적으로 ios_base에는 비트마스크 형태의 iostate라는 멤버가 있습니다. 이 멤버는 eof를 만났을 때 켜지는 eofbit, 입출력이 실패했을 때 켜지는 failbit, 알 수 없는 원인으로 스트림이 손상됐을 때 켜지는 badbit로 구성되어 있습니다. 이 비트들이 모두 0혹은 goodbit가 켜져있다면 스트림의 상태는 양호하다는 뜻입니다.

우선 입력이 부적절할 때 어떤 일이 일어나는 지 확인해 봅시다.

만약 cin이 파일 끝에 도달하면 eof를 만나게 됩니다. 그러면 eofbit가 켜지고, cin이 원하는 문자를 읽지 못하게 되면 failbit가 켜집니다. 그리고 만약 파일 읽기 자체에 에러가 발생하면 badbit가 켜지게 됩니다.

스트림 상태 초기화

스트림의 상태비트를 초기화시키고 싶다면 clear혹은 setstate를 사용하면 됩니다.

clear는 원하는 비트를 초기화시킬 수 있습니다.

clear(); // 모두 해제
clear(eofbit);// eofbit on, 나머지 둘 off(badbit, failbit)

setstate는 오직 원하는 비트만 켜고 끌 수 있습니다.

setstate(eofbit); // eofbit on

입출력의 예외

만약 입력을 받을 때 eofbit가 켜졌다고 해서 예외가 발생할까요??
일반적인 상황에서는 발생하지 않습니다. 하지만 exceptions()메소드를 이용하면 예외의 관리가 가능합니다.
exceptions 메소드는 우리가 직접 설정할 수 있는 eofbit, failbit, badbit를 가진 비트마스크를 갖고있으며 메소드 호출시 이 비트 필드를 리턴합니다.

그리고 clear는 exceptions메소드의 비트 필드와 현재 스트림의 상태를 비교해서 두 값 모두 동일한 비트가 켜져있다면 basic_ios::failure 예외를 발생시킵니다.
이걸 이용하면 예외처리가 가능합니다.

exceptions함수는 다음과 같이 사용합니다.

exceptions(badbit); // badbit on 만약 나중에 스트림에 badbit가 켜지면 예외 발생

참고

std::ios_base :: cplusplus
ios_base 클래스 :: microsoft
C++ 레퍼런스 - ios_base 클래스
C++ IOstream (입출력) 라이브러리

0개의 댓글