본문 바로가기
연구소👨‍💻/CS연구소

[C++] 입출력 스트림

by 신그자체김상범 2024. 7. 8.

프로그래밍 언어의 입출력

프로그램은 사실 어떤 입력을 받고 어떤 출력을 내주는 기계에 불과하다. 그리고 OS에 올라간 프로세스 이외에서 정보를 얻고 주기 위해서는 위해서는 (디스크에서 파일을 읽고 쓴다거나 사용자가 데이터를 입력한다거나) 입출력 시스템에 대한 제대로된 이해가 필요하다.

스트림

스트림이란말은 입출력의 개념에 부합하기 때문에 자칫 그 말의 제대로된 놓치기 쉽다. 한마디로 입출력 자체를 다른말로 스트림이라고 하는거 아니야? 라고 생각할 수 있다.

C++ 입출력은 스트림이고 C는 아니다

하지만 사실 스트림은 그자체로 입출력이라고 할 수 없다. C에서 가장 자주 사용하는 입출력 시스탬은

  1. printf()
  2. scanf()

두가지이다. 사실 알고리즘 푸는 정도에 쓰는데는 아무 문제가 없다. 하지만 좀 더 심화된 시스템에서는 printf와 scanf는 한계가 있다고 한다.

  1. 입출력이 정수, 부동소수점, 스트링, 문자 타입 만을 지원한다.
  2. 에러 처리 기능을 제대로 지원하지 않아 부동 소수점수를 정수로 해석해도 에러를 내지 않는다.
  3. 커스텀 데이터 타입을 처리할 수 없다.
  4. C++에서도 객체지향적이지 않다.

이런 이유에서 C의 입출력은 한계가 있으며 스트림이라고 부르지 않는다고 한다. 하지만 C++의 입출력은 스트림이라고 부르며 대표적인 코드

#include <iostream>

에도 입출력 스트림이라는 이름이 쓰이는 것을 알 수 있다.

스트림과 일반 입출력의 차이 추상화 : 스트림은 연속적으로 입출력을 받을 수 있는 추상화된 시스템이다.

그럼 이런 C의 입출력과 C++의 스트림은 어디서부터 다를까. “전문가를 위한 C++”이라는 책에서는 스트림에 대해서 ‘컨베이어 벨트’라는 말을 사용한다. 스트림은 그 방향과 출발지 소스, 목적지를 지정할 수 있다.

즉 스트림은 연속적으로 입출력을 받을 수 있는 추상화된 시스템이다. 물론 C언어도 연속적으로 입출력을 할 수 있지만 앞서 말한 지원 타입의 한계, 에러처리의 문제가 있고 객체지향적이지 않은 코드때문에 확장성도 낮다.

즉 C++과 C의 입출력 시스템은 추상화라는 측면에서 다르지만 실제 차이는 꽤 크다고 할 수 있다. 실제로 C++의 입출력은 다음과 같은 일을 할 수 있다.

  1. 커스텀 데이터 타입 처리 및 입출력 지원 확장 가능
  2. 에러 처리 기능 구현 가능
  3. 객체지향적 시스템 구현 가능

사실상 C에서 제시된 에러를 많이 개선한 셈이다.

입출력의 종류

프로그램에서 입출력은 다양한 경로를 통해서 일어난다. 우리가 알고리즘을 풀 때 많이 사용하는 콘솔입출력부터 파일 입출력, 그래픽입출력등 다양하다. 각각의 특성은 아래와 같다.

콘솔 입출력

  • 텍스트 기반: 콘솔 입출력은 기본적으로 텍스트 기반입니다. 사용자는 텍스트를 입력하고 텍스트를 출력하는 형태로 상호작용합니다.
  • 표준 스트림: 콘솔 입출력은 cin, cout, cerr, clog와 같은 표준 스트림을 사용합니다.
  • 흐름 제어: 입력과 출력의 흐름이 순차적으로 제어됩니다. 예를 들어, 사용자가 입력할 때까지 프로그램이 대기하고, 입력이 끝나면 그 결과를 출력합니다.

GUI 입출력

  • 그래픽 기반: GUI 입출력은 그래픽 컴포넌트를 사용합니다. 예를 들어, 텍스트 박스, 버튼, 레이블 등의 컴포넌트를 사용하여 입력과 출력을 처리합니다.
  • 이벤트 기반: GUI 프로그램은 이벤트 중심으로 동작합니다. 사용자가 버튼을 클릭하거나 텍스트 박스에 입력을 하면 그 이벤트에 따라 프로그램이 동작합니다.
  • 다양한 입출력 방식: GUI에서는 텍스트 외에도 다양한 형태의 입력과 출력이 가능합니다. 이미지, 그래프, 애니메이션 등을 포함할 수 있습니다.

파일 입출력

  • 파일 기반 : 입출력하는데 파일을 사용한다.
  • 설정 파일이나 저장된 데이터 파일을 읽거나 파일 형태의 데이터를 배치 방식으로 처리하는 데 유용하다.

스트링 스트림

  • 인메모리 스트림 : 텍스트 데이터를 메모리에서 스트림화하는 것이 가능하다. GUI 입출력에서 이를 통해 콘솔, 파일 없이도 스트림으로부터 텍스트 데이터를 구성한 뒤 GUI 요소로 출력할 수 있다.
  • 토큰화기능을 제공하기 때문에 텍스트 구문분석 하기도 편하다.

스트링 스트림의 예시

#include <iostream>
#include <sstream>
#include <string>

// 가상의 텍스트 박스 내용
std::string textBoxContent = "123 456";

void processTextBoxContent() {
    std::stringstream ss(textBoxContent);

    int number1, number2;
    ss >> number1 >> number2;

    std::cout << "텍스트 박스의 내용 처리: " << number1 << ", " << number2 << std::endl;
}

int main() {
    processTextBoxContent();
    return 0;
}

C++스트림은 >>, << 오버로딩을 이용해서 객체 입출력을 지원한다.

C는 객체의 오버로딩을 지원하지 않는다. 즉 printf("Muffin: %m\n", myMuffin); 을 지원하지 않는다.

C++은 이런점에서 << 을 >> 오버로딩함으로써 커스텀 객체의 입출력을 지원해주는 것이다.

이외의 스트림 메서드

cout << 와 cin >> 이외에도 스트림은 사용자들을 위한 몇가지 메소드를 제공한다.

출력

  1. cout : 기본적인 출력 메서드
  2. put : 저수준 문자 하나를 출력
  3. Write :문자열을 입력
  4. Flush : 버퍼에 쌓인 데이터를 즉시 출력

입력

  1. get() : 스트림 데이터를 저수준으로 읽는다.
  2. unget() : 다시 입력 소스 방향으로 데이터를 보낼 수 있음, 이전에 읽은 문자를 스트림으로 되돌린다.
  3. putback() : 입력 스트림을 한 문자만큼 되돌린다, 스트림에 되돌릴 문자를 인수로 받는다.
  4. peek() : get()으로 스트림의 값을 가져올 때 리턴될 값을 미리 본다.
  5. getline() : 입력 스트림에더 데이터를 한 줄 씩 읽는다.
    1. 데이터를 한 줄씩 읽을 일이 아주 많아서 getline( ) 이란 전용 메서드를 제공한다. 이 메서드는 한 줄 크기만큼 미리 설정한 버퍼가 가득 채워질 때까지 문자를 읽는다. 버퍼의 크기를 지정하지 않아도 된다.

입출력 처리를 도와주는 매니퓰레이터

저수준을 지원하는 메소드 이외에도 C++의 스트림은 매니퓰레이터 기능을 통해 단순 입/출력에서 벗어날 수 있다.

매니퓰레이터의 기능은 스트림의 동작을 변경하는 일이다. 대표적인 예시로

출력

  1. endl : 줄 끝 문자를 출력하고 버퍼를 비운다
  2. boolalpha : bool값을 true, false로 출력하거나 1, 0으로 출력하는 값
  3. hex, oct, dec : 출력 정수의 진수 맞추기
  4. setprecision : 분숫값을 표현할 때 소숫점 자리수를 지정
  5. setw : 숫자 데이터 출력할 필드의 너비 지정
  6. setfill : 숫자 데이터 출력할 필드의 너비

입력

  1. boolalpha, noboolalpha : false, true를 입력하면 불값으로 받아들인다.
  2. hex, oct, dec : 16진수, 8진수, 10진수로 읽는다.
  3. skipws, noskipws : 토큰화할 때 공백을 건너뛴다. no면 공백을 하나의 토큰으로 취급한다.

C++ 스트림은 에러를 처리한다.

출력 에러 output error 가 발생하는 경우는 다양하다.

예를 들어 존재하지 않는 파일을 열려고 하거나 디스크가 꽉 차서 쓰기 연산을 처리할 수 없으면 에러가 발생한다. 지금까지 살펴본 스트림 예제는 코드를 간결하게 구성하기 위해 이러한 에러 상황을 신경 쓰지 않았다.

하지만 발생 가능한 모든 에러에 항상 대처하도록 코드를 작성하는 것이 바람직하다.

대표적인 입출력 에러관련 메소드는 다음과 같은 것들이 있다

  1. good()
  2. 지금 입출력 버퍼의 상태를 나타낸다. good()이 true를 리턴하면 스트림을 정상적으로 사용할 수 있단 뜻이다.
  3. bad()
  4. 반대로 bad()는 스트림에 문제가 있는지를 확인한다 이 메서드가 true를 리턴한다는 뜻은 심각한 에러가 발생했다는 것과 같다.
  5. fail()
  6. 이 메서드는 직전의 연산의 성공 여부를 리턴한다. 그래서 어떤 행동 → fail() 검사를 통해 에러를 확인할 수 있다.

만약 여기서 에러를 발생하면 스트림은 ios_base::failure을 통해 exception을 처리할 수 있다.

파일 스트림은 현재 위치를 찾을 수 있다.

파일 스트림은 여태까지 기반으로 했던 콘솔 기반 스트림과 비교할 때 이미 완성된 입력이고 eof처럼 입력의 끝을 알 수 있는 장치가 있다는 것이 특징이다.

그런점에서 파일 스트림은 다음과 같은 특징을 가진다

  1. 파일 스트림은 항상 현재 위치를 추적한다.
  2. 파일 시스템은 에러 처리가 중요하다. 네트워크 저장된 파일을 다루던 중 연결이 끊길 수 있고 로컬 디스크에 파일을 쓰다가 디스크가 가득 찰 수도 있다. 권한이 없는 파일을 열 수도 있다.
  3. 이런 에러 상황을 감지해서 적절히 처리하려면 표준 에러처리 메커니즘을 이용해야 한다.

가장 특이한 것은 파일 스트림이 스트림 입력, 출력에 어떤 위치를 찾을 있다는 점이다. 이것이 가능한 이유는 말그대로 파일의 입력이 이미 정해져있다고 볼 수 있기 때문이다. 이런 위치를 찾고 조정하기 위해 seek(), tell() 함수가 사용된다.

그리고 파일은 반대로

네트워크가 끊긴다거나, 파일을 쓰다가 디스크가 꽉 찬다거나, 권한이 없는 파일을 여는등의 문제가 있을 수 있기 때문에 에러처리를 확실히 히야한다.

또한 파일 스트림 생성자는 파일의 이름과 적용할 모드를 인수로 받는 특징도 있다. 파일 입출력은 텍스트 모드와 바이너리 모드가 따로 있으며 텍스트 모드로 열경우 \n을 기준으로 띄고 바이너리의 경우 정해진 바이트 단위로 읽고 쓴다.

스트림들은 서로 연결할 수 있다.

마지막으로 C++의 입출력 장치들은 서로 연결될 수 있다고한다. 두개의 입출력이 서로 연결되어 있으면 입력 스트림에서 데이터를 읽으면 즉시 출력 스트림으로 내보낸다. 그리고 반대도 유지된다.

두 스트림을 연결하는 방법은

outFile.tie(&anotherOutputFile);

같이 tie로 연결하는 방식이 있다

이외에도 양방향 입출력등 다뤄야할 게 많지만 너무 길어진 관계로 오늘은 여기까지 적도록 하겠다.

'연구소👨‍💻 > CS연구소' 카테고리의 다른 글

[C++] 스레드 Lock  (1) 2024.09.22
[C++] template <1>  (8) 2024.09.07
[C++] 예외 처리  (0) 2024.06.26
[C++] 다형성과 오버로딩, 오버라이딩  (0) 2024.06.22
[네트워크] TCP, UDP 프로토콜  (0) 2024.06.19