[C++] 입출력 (istream, ostream)

·2023년 8월 24일
0

C++

목록 보기
14/17
post-custom-banner

C++ 입출력 라이브러리

C++ 입출력 라이브러리의 구성

ios_base : 스트림의 입출력 형식 관련 데이터 처리
(예) 실수 형을 출력할 때의 정밀도를 어떤 식으로 할건지?, 정수를 몇진수로 출력할지

ios : 스트림 버퍼를 초기화 및 스트림 상태 관리
(참고) 스트림 버퍼
데이터를 내보내거나 받아들이기 전에 임시로 저장하는 곳
사용자가 1바이트 씩 읽는 다고 했을 때 실제로 프로그램은 1byte씩 읽는 것이 아님
한 덩어리를 한꺼번에 읽어서 스트림 버퍼에 잠시 저장해 놓은 뒤 사용자가 요청할 때 마다 1byte씩 꺼냄
(하드디스크에서 읽어오는 작업이 매우 느리기 때문에 1byte씩 읽으면 엄청난 딜레이가 발생함)
출력도 마찬가지로 하드에 바로 쓰는 것이 아니라 버퍼에 보관한 후 어느 정도 모이면 출력함

스트림 상태 관리

스트림 상태를 관리하는 플래그는 4개가 정의되어 있음 (goodbit, badbit, eofbit, failbit)

  • goodbit : 스트림에 입출력 작업이 가능할 때
  • badbit : 스트림에 복구 불가능한 오류 발생시
  • failbit : 스트림에 복구 가능한 오류 발생시
  • eofbit : 입력 작업시에 EOF 도달시
// 주의할 점
#include <iostream>
using namespace std;
int main() 
{
  int t;
  while (true) 
  {
    std::cin >> t;
    std::cout << "입력 :: " << t << std::endl;
    if (t == 0) break;
  }
}

위 코드에서 t의 값으로 c를 넣는 경우 무한 루프에 빠지게 됨 (failbit)
타입에 맞지 않는 값을 넣어서 오류가 발생하는 경우 failbit가 켜지게 됨
입력값을 받지 않고 리턴해버림
문제는 리턴해버리면서 버퍼에 남아있는 'c\n' 문자열에는 손을 대지 않음 -> 무한대로 읽는 문제 발생

// 해결 방안
#include <iostream>
#include <string>

int main() 
{
  int t;
  while (std::cin >> t) 
  {
    std::cout << "입력 :: " << t << std::endl;
    if (t == 0) break;
  }
}

이렇게 하면 무한 루프에 빠지지 않게 됨

ios에는 operator void() const; 함수가 있음
이 함수는 ios 객체를 void*로 변환 해 줌
nullptr가 아닌 값을 리턴하는 조건이 failbit와 badbit가 모두 off일 때 이므로
c 입력 시 failbit가 켜지니까 ios 객체 -> void
-> bool 변환을 통해 while문을 빠져나오게 됨

그러나 계속해서 입력을 받을 수 없게 됨

// 계속해서 입력을 받는 방법
#include <iostream>
#include <string>

int main() {
  int t;
  while (true) {
    std::cin >> t;
    std::cout << "입력 :: " << t << std::endl;
    if (std::cin.fail()) {
      std::cout << "제대로 입력해주세요" << std::endl;
      std::cin.clear();            // 플래그들을 초기화 하고
      std::cin.ignore(100, '\n');  // 개행문자가 나올 때 까지 무시한다
    }
    if (t == 1) break;
  }
}

if(std::cin.fail())은 failbit가 true거나 badbit가 true면 true를 리턴함
숫자가 아닌 값을 입력하면 failbit가 true이므로, std::cin.fail()은 true가 되어 조건문을 실행하게 됨
그 후 std::cin.clear()를 통해 플래그들을 초기화 하고,
std::cin.ignore(100, '\n')을 통해 첫 번째 인자 만큼 두 번째 인자가 나올 때 까지 버퍼에서 무시함

만약 100자 이상을 집어 넣은 경우에는 ignore 함수가 버퍼에 남아 있는 문자들이 다 지워질 때 까지 호출됨

iostream : 실제로 입력을 수행하는 클래스
대표적으로 operaor>>가 이 안에 정의되어 있는 연산자임
cin은 istream 클래스의 객체 중 하나


형식 플래그와 조작자

ios_base에서 스트림의 입출력 형식을 변환한 예

#include <iostream>
#include <string>

int main() 
{
  int t;
  while (true) 
  {
    std::cin >> t;
    std::cout << "입력 :: " << t << std::endl;
    if (std::cin.fail()) 
    {
      std::cout << "제대로 입력해주세요" << std::endl;
      std::cin.clear();            // 플래그들을 초기화 하고
      std::cin.ignore(100, '\n');  // 개행문자가 나올 때 까지 무시한다
    }
    if (t == 1) break;
  }
}

실행 결과
ff
입력 :: 255
0xFF
입력 :: 255
123
입력 :: 291
ABCDE
입력 :: 703710

16진수 입력을 잘 받게 됨 (출력 형식은 바꾸지 않았으므로 10진수로 출력됨)
std::cin.setf(ios_base::hex, ios_base::basefield);
setf 함수에 인자를 1개 넣으면 인자로 준 형식 플래그를 적용하고,
setf 함수에 인자를 2개 넣으면 두 번째 인자의 내용(위에서 basefield)을 초기화 하고, 첫 번째 인자를 적용함
(참고) basefield는 몇 진법으로 처리할 지 보관하는 곳임

// 16진수를 받는 또 다른 방법 (조작자 사용)
#include <iostream>
#include <string>

int main() {
  int t;
  while (true) {
    std::cin >> std::hex >> t;
    std::cout << "입력 :: " << t << std::endl;
    if (std::cin.fail()) {
      std::cout << "제대로 입력해주세요" << std::endl;
      std::cin.clear();           // 플래그들을 초기화 하고
      std::cin.ignore(100, 'n');  //개행문자가 나올 때까지 무시한다
    }
    if (t == 0) break;
  }
}

std::cin >> std::hex >> t; -> hex가 cin에서 수를 받는 방식을 바꿔버림
이와 같이 스트림을 조작하여 입력 혹은 출력 방식을 바꿔주는 함수를 조작자라고 부름
위에서 사용한 형식 플래그(ios_base::hex)와 이름만 같지 아예 다름 (<- 이건 상수 값임)

std::ios_base& hex(std::ios_base& str);

다시 보는 std::endl

endl는 출력을 관장하는 ostream에 정의되어 있는 조작자로 한 줄 개행문자를 출력하는 것 외에도
버퍼를 모두 내보내는 flush 역할도 수행함

post-custom-banner

0개의 댓글