IO Stream

Date:     Updated:

카테고리:

태그:

모두의 코드 씹어먹는 c++ 자료를 보고 정리한 내용입니다.


io1

  • ios_base : 스트림의 입출력 형식 관련 데이터 처리
  • ios : 스트림 버퍼 관리 & 입출력 작업의 상태 관리
  • iostream : 실제로 입력/출력을 수행하는 클래스 (operator », operator » 정의 )
  • fstream : 파일 입출력 수행하는 클래스

스트림 상태 관리

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

io2

  • 위 코드에서 s 하나를 입력하면 무한 루프에 빠짐. Why??

ios 클래스에서 [스트림의 상태를 관리] → 상태를 관리하는 4개의 플래그(비트 하나) 정의

  • goodbit : 스트림에 입출력 작업이 가능할 때
  • badbit : 스트림에 복구 불가능한 오류 발생 시
  • failbit : 스트림에 복구 가능한 오류 발생 시
  • eofbit : 입력 작업시에 EOF 도달 시

타입에 맞지 않는 값을 넣어서 오류가 발생하는 경우 failbit 플래그 ON → 입력을 받지 않고 리턴해버림 → 버퍼에 문자열이 그대로 남아있음 → 읽고 또 읽고, 또 읽고, ..

while (std::cin >> t)
{
	// ...
}
  • s를 입력하면 operator >>cin 객체의 failbit
  • std::cin >> t 이후 cin 객체가 리턴 (operator >>는 호출한 자기 자신을 리턴)
  • 리턴된 cin객체가 while문의 조건식으로 들어가기 때문에 컴파일러가 적절한 타입 변환을 찾게 됨
  • ios(cin) 객체 내부에는 operator void*() const 함수가 존재 (void포인터로의 형변환 연산자)
  • 즉, ios객체 → void*bool (false) → while문 탈출
  • 참고) NULL 포인터는 bool 상 false

[문제는 해결했지만, 입력을 계속 진행할 수는 없음…]

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;
  }
}
  • std::cin.fail() : failbit or badbit 가 true면 true 반환
  • std::cin.clear() : 플래그 초기화
  • std::cin.ignore(100, '\n') : 두 번째 인자가 나올 때 까지 최대 첫 번째 인자만큼 버퍼 비우기

Format Flag & Manipulator

입력한 수를 16진수로 처리하고 싶음!

[Format Flag]

std::cin.setf(std::ios_base::hex, std::ios_base::basefield);
  • 몇 진법으로 수를 처리할지 보관하는 basefield의 값을 초기화하고, 16진법 hex 플래그를 적용
  • std::ios_base::hex : hex는 단순한 상수 값
  • 사실 문자열로 입력받아서 처리해도 되지만, 이것저것 처리할 것들이 많기 때문에 IO 라이브러리에서 처리하는 것이 깔끔

[Manipulator]

std::cin >> std::hex >> t;
  • std::hex : 스트림을 조작하여 입력 혹은 출력 방식을 바꿔주는 함수!
std::ios_base& hex(std::ios_base& str);

istream& operator>>(ios_base& (*pf)(ios_base&));

str.setf(std::ios_base::hex, std::ios_base::basefield)
  • hexios_base 객체의 레퍼런스를 받고, 다시 그 객체를 리턴하는 함수
  • operator >> 에 함수포인터( ios_base 레퍼런스를 파라미터로 받고, 리턴하는 함수) 오버로딩 되어있음
  • std::hex가 하는 일은 setf와 동일

이 외에도 여러 Manipulator(조작자) 존재

boolalpha : turefalse를 1과 0으로 처리하는 대신 문자열 그대로 입력, 출력 left / right : 왼쪽 / 오른쪽 정렬 endl : 한 줄 개행문자 출력 & 버퍼 Flush

Stream Buffer

io3

모든 입출력 객체들은 대응되는 스트림 객체를 가지고 있다. 따라서 c++의 입축력 라이브러리에는 이에 대응되는 스트림 버퍼 클래스도 있는데, 바로 streambuf 클래스 이다. streambuf클래스는 스트림의 상태를 나타내기 위해 아래와 같은 세 개의 포인터를 가지고 있다.

  • 버퍼의 시작 부분을 가리키는 시작 포인터
  • 읽을 문자를 가리키고 있는 포인터 (스트림 위치 지정자)
  • 버퍼의 끝 부분을 가리키는 포인터 또한 steambuf 클래스는 입력 버퍼와 출력 버퍼를 구분해 [get area][put area] 로 구분하고, 이에 따라 각각을 가리키는 포인터도 gp를 붙여서 표현한다.

스트림 버퍼 다루기 예제)

int main() {
  std::string s;
  std::cin >> s;

  // 위치 지정자를 한 칸 옮기고, 그 다음 문자를 훔쳐본다 (이 때는 움직이지 않음)
  char peek = std::cin.rdbuf()->snextc();
  if ( std::cin.fail() ) std::cout << "Failed";
  std::cout << "두 번째 단어 맨 앞글자 : " << peek << std::endl;
  std::cin >> s;
  std::cout << "다시 읽으면 : " << s << std::endl;
}

컴파일 결과)

hello world  번째 단어  앞글자 : w 다시 읽으면 : world

char peek = std::cin.rdbuf()->snextc();
  • rdbuf() : [cin] 객체가 입력을 수행하고 있던 steambuf 객체 포인터 리턴.
    • 이때 [cin] 객체가 istream 이므로, [get area]만 존재
  • snextc() : 스트림 위치 지정자를 한 칸 전진시킨 후, 그자리에 해당하는 문자를 엿봄
    • 원래 해당 문자를 읽으면 위치 지정자를 한 칸 전진시킴 (엿보는 것은 전진 X)

io4

io5

Stream Buffer를 도입한 중요한 이유 중 한 가지는, 1바이트짜리 문자 뿐만이 아니라. 다중 바이트 문자들 (UTF-8과 같이) 에 대한 처리도 가능하게 하기 위함이다.

파일 입출력

fstream : iostream을 상속받아 파일 처리 기능을 추가한 라이브러리

int main() {
  // 파일 읽기 준비
  std::ifstream in("test.txt");
  std::string s;

  if (in.is_open()) {
    in >> s;
    std::cout << "입력 받은 문자열 :: " << s << std::endl;
  } else {
    std::cout << "파일을 찾을 수 없습니다!" << std::endl;
  }
  return 0;
}

cin이나 cout의 경우 이미 표준 입출력에 연동이 되어 있지만, 파일 입출력의 경우 어느 파일에 입출력을 해야할 지 지정을 해야한다. 이는 ifstream 객체 생성자에 연동하고자 하는 파일의 경로를 전달하면 된다. 한 가지 흥미로운 점은, C 언어와 달리 close를 해줄 필요가 없다. 왜 그렇냐면, 이미 ifstream 객체 소멸자에 구현이 되어있기 때문이다. 다만, 같은 객체가 새로운 파일에서 입력을 받고자 한다면 기존 파일 스트림과의 연결을 종료하고 새로운 파일을 연결해주어야 한다 (close, open) .

리틀 에디안

int main() {
  // 파일 읽기 준비
  std::ifstream in("test.txt", std::ios::binary);
  std::string s;

  int x;
  if (in.is_open()) {
    in.read((char* )(&x), 4);
    std::cout << std::hex << x << std::endl;
  } else {
    std::cout << "파일을 찾을 수 없습니다!" << std::endl;
  }

  return 0;
}
Input file Compile Result
io6 1 io7 1

파일 입출력에서 주의해야 할 것은 있다. 우리가 쓰는 CPU의 경우 리틀 에디안이라 해서, 높은 주소값에 높은 자리수가 온다고 생각하면 된다. 따라서 각각의 바이트 EF / BB / BF / EC 가 거꾸로 EC / BF / BB / EF 이렇게 int 변수에 기록된다.

std::ifstream in("test.txt", std::ios::binary);
  • ifstream 객체를 생성할 때 생성자에 옵션으로 문자열 데이터를 받는게 아니라 그냥 이진 그대로의 값을 받음
  • Default는 문자열.
int x;
in.read((char* )(&x), 4);
  • read 함수는 말 그대로 두 번째 인자(4) 바이트 만큼의 내용을 읽으라는 내용
  • 첫 번째 인자에는 해당하는 버퍼를 전달해주어야 함
  • 위 명령어의 경우 int 변수를 4바이트 짜리 char 배열이라 생각하라는 의미

파일 전체 읽기

int main() {
  // 파일 읽기 준비
  std::ifstream in("test.txt");
  std::string s;

  if (in.is_open()) {
    // 위치 지정자를 파일 끝으로 옮긴다.
    in.seekg(0, std::ios::end);

    // 그리고 그 위치를 읽는다. (파일의 크기)
    int size = in.tellg();

    // 그 크기의 문자열을 할당한다.
    s.resize(size);

    // 위치 지정자를 다시 파일 맨 앞으로 옮긴다.
    in.seekg(0, std::ios::beg);

    // 파일 전체 내용을 읽어서 문자열에 저장한다.
    in.read(&s[0], size);
    std::cout << s << std::endl;
  } else {
    std::cout << "파일을 찾을 수 없습니다!" << std::endl;
  }

  return 0;
}
  • seekg() : 파일 위치 지정자 움직임 (파일 내 두 번째 인자로부터 첫 번째 인자만큼 떨어진 위치)
  • tellg() : 파일의 시작 지점으로 부터 위치 지정자 까지의 거리 반환

파일 한 줄씩 읽기

int main() {
  // 파일 읽기 준비
  std::ifstream in("test.txt");
  char buf[100];

  if (!in.is_open()) {
    std::cout << "파일을 찾을 수 없습니다!" << std::endl;
    return 0;
  }

  while (in) {
    in.getline(buf, 100);
    std::cout << buf << std::endl;
  }

  return 0;
}

getline()

  • 파일에서 개행문자(‘ /n’) 가 나올 때까지 (최대 지정한 크기 - 1) 만큼 읽음
  • -1을 하는 이유는 bufer 맨 마지막 문자로 널 종료 문자를 넣어줘야 하기 때문
  • 개행문자 말고도 다른 문자 지정 가능

while (in)

  • ifstream에는 자기 자신을 bool로 캐스팅하는 캐스팅 연산자 operator bool()가 오버로딩 되어 있음
  • 다음 입력 작업이 성공적이여야 true 반환

하지만 getline() 함수는 개행문자(혹은 지정한 문자)가 나오기 전에 지정한 버퍼의 크기가 다 차면 failbit 키게 됨. 따라서 버퍼의 크기가 작다면 정상적으로 데이터를 받을 수 없음 → 매우 귀찮기 때문에 std::string 에서 getline() 함수 제공

  std::string s;
  while (in) {
    getline(in, s);
    std::cout << s << std::endl;
  }

파일 쓰기

int main() {
  // 파일 쓰기준비
  std::ofstream out("test.txt", std::ios::app);

  std::string s;
  if (out.is_open()) {
    out << "덧붙이기";
  }

  return 0;
}
  • ios::app : 파일의 기존 내용을 지우지 않고, 그 뒤에 붙여 씀
  • ios::ate : 파일을 열 때 위치 지정자가 파일 끝을 가리키고 있음
  • ios::trunc : 파일 스트림을 열면 기존 내용들을 모두 지워버림. [디폴트 설정]

app vs ate app은 원본 파일의 내용을 건드릴 수 없음 반면 ate의 경우 위치 지정자를 이전으로 옮겨 수정이 가능

std::ofstream 연산자 오버로딩

class Human {
  std::string name;
  int age;

 public:
  Human(const std::string& name, int age) : name(name), age(age) {}
  std::string get_info() {
    return "Name :: " + name + " / Age :: " + std::to_string(age);
  }

  friend std::ofstream& operator<<(std::ofstream& o, Human& h);
};

std::ofstream& operator<<(std::ofstream& o, Human& h) {
  o << h.get_info();
  return o;
}
int main() {
  // 파일 쓰기 준비
  std::ofstream out("test.txt");

  Human h("이재범", 60);
  out << h << std::endl;

  return 0;
}
// test.txt
Name ::  이재범 / Age :: 60

문자열 스트림

int main() {
  std::istringstream ss("123");
  int x;
  ss >> x;

  std::cout << "입력 받은 데이터 :: " << x << std::endl;

  return 0;
}

std::istringstream : 마치 문자열을 하나의 스트림이라 생각하게 해주는 가상화 장치 위 코드는 마치 ss란 파일에 문자열 “123”을 기록해놓고 거기서 입력을 받는 것과 같음

std::ostringstream 함수 역시 마찬가지



맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기