본문 바로가기

텍스트 파일 읽기

std::ifstream으로 파일을 열고 토큰·줄 단위 데이터를 읽으며 EOF와 스트림 상태에 맞게 오류를 처리합니다.

텍스트 파일은 사람이 읽을 수 있는 글자로 데이터를 저장한 파일입니다. 설정 파일, 로그, 표 형식의 데이터처럼 흔히 쓰이며, 메모장으로 열어 내용을 바로 확인하고 고칠 수 있습니다.

이 글은 std::ifstream으로 텍스트 파일을 열고, 공백으로 구분된 값과 한 줄 단위의 데이터를 읽는 방법을 다룹니다. 두 방식을 섞을 때 남는 줄바꿈 문제와, 읽기가 멈춘 이유를 상태 플래그로 구분하는 방법도 함께 봅니다. 예제의 입력 파일은 실행하는 작업 폴더에 둡니다.

std::ifstream으로 텍스트 파일 열기

텍스트 파일은 입력 파일 스트림(input file stream)인 std::ifstream으로 읽습니다. 스트림과 파일 입출력 개요에서 본 것처럼, 객체를 만들면서 파일 이름을 넘기거나 객체를 만든 뒤 open을 부릅니다.

open_ifstream.cpp
#include <fstream>
#include <iostream>

int main() {
    std::ifstream first("data.txt");
    if (!first.is_open()) {
        std::cerr << "cannot open data.txt\n";
    }

    std::ifstream second;
    second.open("another_data.txt");
    if (!second.is_open()) {
        std::cerr << "cannot open another_data.txt\n";
    }
}

std::ifstream은 읽기 모드로 열며, 파일이 없으면 새로 만들지 않고 열기에 실패합니다. 그래서 읽기 전에 반드시 is_open()으로 연결을 확인합니다. 두 스트림은 main이 끝나 블록을 벗어날 때 자동으로 닫힙니다.

공백으로 구분된 값 읽기

std::cin처럼 파일 스트림에도 추출 연산자 >>를 쓸 수 있습니다. >>는 앞의 공백, 탭, 줄바꿈을 건너뛰고 다음 값을 읽으므로, 값 사이의 공백 개수나 줄 구분과 상관없이 차례로 읽습니다.

numbers.txt
100 John 3.14
200 Jane 2.718
read_tokens.cpp
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream in("numbers.txt");
    if (!in.is_open()) {
        std::cerr << "cannot open numbers.txt\n";
        return 1;
    }

    int id;
    std::string name;
    double value;
    while (in >> id >> name >> value) {
        std::cout << "id " << id << ", name " << name
                  << ", value " << value << '\n';
    }

    if (in.bad()) {
        std::cerr << "I/O error\n";
        return 1;
    } else if (in.eof()) {
        std::cout << "stopped at end of file\n";
    } else if (in.fail()) {
        std::cerr << "bad format\n";
        return 1;
    }
}
id 100, name John, value 3.14
id 200, name Jane, value 2.718
stopped at end of file

in >> id >> name >> value는 스트림 자신을 돌려주므로, 조건은 세 값을 모두 읽은 뒤 스트림이 실패 상태가 아닌지를 묻습니다. 두 번째 레코드 뒤에서 다음 id를 읽으려다 파일 끝에 닿으면 실패해 루프가 끝나고, 이때 eofbit가 설정되어 있으므로 정상적인 끝으로 판단합니다.

다만 eof()가 참이라고 마지막 레코드가 완전했다는 뜻은 아닙니다. 마지막 줄이 200 Jane처럼 값 하나가 빠진 채 끝나면, value를 읽다가 파일 끝에 닿아 eofbit와 failbit가 함께 설정됩니다. 그 레코드는 출력되지 않지만 위 코드는 정상 종료로 보고하므로, 불완전한 레코드를 오류로 다뤄야 한다면 레코드를 한 줄씩 읽어 필드 수를 따로 검사합니다.

한 줄씩 읽기

한 줄 전체가 의미 있는 단위라면 std::getline으로 줄바꿈 앞까지 읽습니다. 공백이 포함된 문장도 한 번에 읽을 수 있습니다.

quotes.txt
Imagination is more important than knowledge.
The only way to do great work is to love what you do.
Life is what happens when you're busy making other plans.
read_lines.cpp
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream in("quotes.txt");
    if (!in.is_open()) {
        std::cerr << "cannot open quotes.txt\n";
        return 1;
    }

    std::string line;
    int number = 1;
    while (std::getline(in, line)) {
        std::cout << number++ << ": " << line << '\n';
    }
}
1: Imagination is more important than knowledge.
2: The only way to do great work is to love what you do.
3: Life is what happens when you're busy making other plans.

std::getline은 줄바꿈을 입력에서 꺼내 버리고, line에는 줄바꿈 앞까지만 넣습니다. 마지막 줄에 줄바꿈이 없어도 글자를 읽었다면 성공으로 처리되므로, 그 줄도 빠짐없이 출력됩니다. 셋째 인자로 구분 문자를 넘기면 std::getline(in, field, ',')처럼 쉼표 앞까지 읽을 수도 있습니다.

>>와 getline을 섞을 때

>>는 값을 읽은 뒤 그 뒤의 줄바꿈을 입력에 남겨 둡니다. 그래서 숫자를 >>로 읽은 직후 getline을 부르면, 남은 줄바꿈 앞의 빈 문자열을 읽고 바로 돌아옵니다.

user_data.txt
100
Alice Wonderland
200
Bob The Builder

아래 그림은 같은 입력에서 두 경로를 비교합니다. 100을 읽은 뒤 바로 getline을 부르면 빈 이름을 얻고, ignore로 현재 줄의 나머지를 버린 뒤 부르면 다음 줄의 Alice Wonderland를 공백까지 그대로 읽습니다.

토큰 입력 뒤 줄 입력이 보는 버퍼

같은 100 개행 Alice Wonderland 입력을 두 독립 경로에서 읽으면 바로 getline은 빈 이름, ignore 후 getline은 이름 전체가 된다. 다음 문자/추출 결과 상태를 나란히 추적한다.

서식 입력으로 ID 토큰을 읽은 뒤 남은 구분 문자를 처리하지 않고 getline을 호출하는 경로와 현재 줄을 버린 뒤 다음 전체 줄을 읽는 경로를 비교하는 C++ 입력 시퀀스

C++ · formatted input → line input · sequence

읽기 단위가 바뀌면 다음 문자의 위치부터 확인한다

operator>>는 토큰을 변환하고 뒤따르는 구분 문자를 남길 수 있습니다. 곧바로 getline()을 호출할지, 현재 줄을 버리고 다음 줄부터 읽을지는 입력 형식이 결정합니다.

동일입력에서곧바로줄읽기와현재줄폐기후읽기는대안이다두대안은동일100개행AliceWonderland개행입력조건이다.첫경로는getline→빈문자열,둘째는ignore→getline→AliceWonderland로각1/2간선이며서로연결하지않는다.첫경로뒤에ignore를연속실행한다는뜻이아니다.원desktop의ALT조건과값입력/반환은본문시퀀스를참조한다.ID100 · 다음 문자는 줄바꿈바로 getline구분자부터 읽음빈 이름 문자열개행은 소비됨ID100 · 다음 문자는 줄바꿈ignore(max, 줄바꿈)현재 줄 끝까지 폐기다음 getline다음 이름 줄 읽기Alice Wonderland공백 포함 보존
토큰 입력에서 줄 입력으로 전환하는 시퀀스 호출 코드가 ifstream에서 숫자 100을 읽으면 뒤의 개행이 다음 문자로 남는다. 바로 getline을 호출하면 빈 문자열을 얻고, ignore로 현재 줄 끝까지 버린 뒤 getline을 호출하면 다음 줄의 Alice Wonderland를 얻는다. ALT [버퍼 처리를 생략] [다음 줄이 한 레코드] operator>>(id) id = 100 · next = '\n' getline(name) name = "" · '\n' consumed ignore(max, '\n') current line discarded getline(name) Alice Wonderland 호출 코드 std::ifstream formatted · line input
버퍼 100\nAlice Wonderland\n
바로 getline

id = 100 다음 문자는 \n

getline(name)이 그 개행을 줄 끝으로 소비해 name == ""가 됩니다.

현재 줄 폐기

ignore(max, '\n') 뒤 다음 줄 읽기

현재 줄의 나머지와 개행을 버린 뒤 getline(name)이 Alice Wonderland를 읽습니다.

operator>>

토큰을 변환한다

기본 skipws는 선행 공백을 건너뛰지만, 변환한 토큰 뒤의 구분 문자는 다음 입력에 남을 수 있습니다.

getline

구분 문자까지 소비한다

한 줄의 내용을 문자열에 저장하고 구분 문자 자체는 버퍼에서 제거하되 결과 문자열에는 넣지 않습니다.

std::ws

모든 선행 공백을 건너뛴다

빈 줄이나 들여쓰기를 보존해야 한다면 의미가 달라질 수 있으므로 입력 형식에 맞을 때만 사용합니다.

ignore

현재 줄의 나머지를 버린다

max()에 streamsize가 표현할 수 있는 최댓값을 쓰고 '\n'을 구분자로 주면 현재 줄 끝까지 폐기합니다.

핵심은 무조건 개행을 지우는 것이 아니라 다음 필드가 같은 줄인지 다음 줄인지 먼저 정하는 것입니다. 입력 단위가 바뀌는 지점에서는 버퍼의 다음 문자와 스트림 상태를 함께 확인합니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목동일 입력에서 곧바로 줄 읽기와 현재 줄 폐기 후 읽기는 대안이다
관계·조건 설명두 대안은 동일한 100 개행 Alice Wonderland 개행 입력 조건을 사용한다. 첫 경로는 getline→빈 문자열, 둘째는 ignore→getline→Alice Wonderland로 각각 1개/2개 간선이며 서로 연결하지 않는다. 첫 경로 뒤에 ignore를 연속 실행한다는 뜻이 아니다. 원래 가로 그림의 ALT 조건과 값 입력/반환은 본문 시퀀스를 참조한다.
노드·연결선 라벨ID 100 · 다음 문자는 줄바꿈
예의 값과 변화
관찰·상황값·보충 정보변화·이유
숫자 추출 뒤

같은 입력 100 개행 Alice Wonderland에서 시작한다.

  • 추출한 수: 100
  • 다음 문자: 개행
  • formatted 추출은 구분자인 개행을 남길 수 있다.
바로 getline

첫 번째 독립 경로에서는 개행이 즉시 읽힌다.

  • 이름: 빈 문자열
  • 원인: 남은 개행
  • getline은 다음 개행까지 읽으므로 첫 줄은 비어 있다.
ignore 뒤 getline

다른 사본에서는 개행까지 제거한 뒤 읽는다.

  • 제거: 남은 개행
  • 이름: Alice Wonderland
  • 입력 경계를 넘긴 뒤 getline이 공백을 포함한 이름을 받는다.

토큰 입력 뒤 줄 입력이 보는 버퍼

같은 100 개행 Alice Wonderland 입력을 두 독립 경로에서 읽으면 바로 getline은 빈 이름, ignore 후 getline은 이름 전체가 된다. 다음 문자/추출 결과 상태를 나란히 추적한다.

서식 입력으로 ID 토큰을 읽은 뒤 남은 구분 문자를 처리하지 않고 getline을 호출하는 경로와 현재 줄을 버린 뒤 다음 전체 줄을 읽는 경로를 비교하는 C++ 입력 시퀀스

C++ · formatted input → line input · sequence

읽기 단위가 바뀌면 다음 문자의 위치부터 확인한다

operator>>는 토큰을 변환하고 뒤따르는 구분 문자를 남길 수 있습니다. 곧바로 getline()을 호출할지, 현재 줄을 버리고 다음 줄부터 읽을지는 입력 형식이 결정합니다.

동일입력에서곧바로줄읽기와현재줄폐기후읽기는대안이다두대안은동일100개행AliceWonderland개행입력조건이다.첫경로는getline→빈문자열,둘째는ignore→getline→AliceWonderland로각1/2간선이며서로연결하지않는다.첫경로뒤에ignore를연속실행한다는뜻이아니다.원desktop의ALT조건과값입력/반환은본문시퀀스를참조한다.ID100 · 다음 문자는 줄바꿈바로 getline구분자부터 읽음빈 이름 문자열개행은 소비됨ID100 · 다음 문자는 줄바꿈ignore(max, 줄바꿈)현재 줄 끝까지 폐기다음 getline다음 이름 줄 읽기Alice Wonderland공백 포함 보존
토큰 입력에서 줄 입력으로 전환하는 시퀀스 호출 코드가 ifstream에서 숫자 100을 읽으면 뒤의 개행이 다음 문자로 남는다. 바로 getline을 호출하면 빈 문자열을 얻고, ignore로 현재 줄 끝까지 버린 뒤 getline을 호출하면 다음 줄의 Alice Wonderland를 얻는다. ALT [버퍼 처리를 생략] [다음 줄이 한 레코드] operator>>(id) id = 100 · next = '\n' getline(name) name = "" · '\n' consumed ignore(max, '\n') current line discarded getline(name) Alice Wonderland 호출 코드 std::ifstream formatted · line input
버퍼 100\nAlice Wonderland\n
바로 getline

id = 100 다음 문자는 \n

getline(name)이 그 개행을 줄 끝으로 소비해 name == ""가 됩니다.

현재 줄 폐기

ignore(max, '\n') 뒤 다음 줄 읽기

현재 줄의 나머지와 개행을 버린 뒤 getline(name)이 Alice Wonderland를 읽습니다.

operator>>

토큰을 변환한다

기본 skipws는 선행 공백을 건너뛰지만, 변환한 토큰 뒤의 구분 문자는 다음 입력에 남을 수 있습니다.

getline

구분 문자까지 소비한다

한 줄의 내용을 문자열에 저장하고 구분 문자 자체는 버퍼에서 제거하되 결과 문자열에는 넣지 않습니다.

std::ws

모든 선행 공백을 건너뛴다

빈 줄이나 들여쓰기를 보존해야 한다면 의미가 달라질 수 있으므로 입력 형식에 맞을 때만 사용합니다.

ignore

현재 줄의 나머지를 버린다

max()에 streamsize가 표현할 수 있는 최댓값을 쓰고 '\n'을 구분자로 주면 현재 줄 끝까지 폐기합니다.

핵심은 무조건 개행을 지우는 것이 아니라 다음 필드가 같은 줄인지 다음 줄인지 먼저 정하는 것입니다. 입력 단위가 바뀌는 지점에서는 버퍼의 다음 문자와 스트림 상태를 함께 확인합니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목동일 입력에서 곧바로 줄 읽기와 현재 줄 폐기 후 읽기는 대안이다
관계·조건 설명두 대안은 동일한 100 개행 Alice Wonderland 개행 입력 조건을 사용한다. 첫 경로는 getline→빈 문자열, 둘째는 ignore→getline→Alice Wonderland로 각각 1개/2개 간선이며 서로 연결하지 않는다. 첫 경로 뒤에 ignore를 연속 실행한다는 뜻이 아니다. 원래 가로 그림의 ALT 조건과 값 입력/반환은 본문 시퀀스를 참조한다.
노드·연결선 라벨ID 100 · 다음 문자는 줄바꿈
예의 값과 변화
관찰·상황값·보충 정보변화·이유
숫자 추출 뒤

같은 입력 100 개행 Alice Wonderland에서 시작한다.

  • 추출한 수: 100
  • 다음 문자: 개행
  • formatted 추출은 구분자인 개행을 남길 수 있다.
바로 getline

첫 번째 독립 경로에서는 개행이 즉시 읽힌다.

  • 이름: 빈 문자열
  • 원인: 남은 개행
  • getline은 다음 개행까지 읽으므로 첫 줄은 비어 있다.
ignore 뒤 getline

다른 사본에서는 개행까지 제거한 뒤 읽는다.

  • 제거: 남은 개행
  • 이름: Alice Wonderland
  • 입력 경계를 넘긴 뒤 getline이 공백을 포함한 이름을 받는다.
mix_token_and_line.cpp
#include <fstream>
#include <iostream>
#include <limits>
#include <string>

int main() {
    std::ifstream in("user_data.txt");
    if (!in.is_open()) {
        std::cerr << "cannot open user_data.txt\n";
        return 1;
    }

    int id;
    std::string fullName;
    while (in >> id) {
        in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (!std::getline(in, fullName)) {
            std::cerr << "missing name for id " << id << '\n';
            return 1;
        }
        std::cout << "id " << id << ", name " << fullName << '\n';
    }
}
id 100, name Alice Wonderland
id 200, name Bob The Builder

ignore는 지정한 개수만큼, 또는 구분 문자를 만날 때까지 글자를 버립니다. std::numeric_limits<std::streamsize>::max()를 넘기면 개수 제한 없이 줄바꿈까지 버린다는 뜻입니다. 이름 줄을 읽지 못하면 레코드가 불완전하므로 오류로 끝냅니다.

ignore 대신 std::getline(in >> std::ws, fullName)을 쓸 수도 있습니다. std::ws는 앞의 공백과 줄바꿈을 모두 건너뛰므로, 이름 앞의 공백이 사라지고 빈 이름 줄도 건너뜁니다. 두 방법은 문법만 다른 것이 아니라 받아들이는 파일 형식이 다르므로, 파일의 규칙에 맞는 쪽을 고릅니다.

더 깊이: 줄을 읽은 뒤 숫자로 바꾸는 std::from_chars

한 줄을 std::getline으로 읽은 뒤 그 안의 숫자를 값으로 바꿀 때는 C++17의 <charconv>에 있는 std::from_chars를 쓸 수 있습니다. std::from_chars(first, last, value)는 [first, last)의 글자를 읽어 value에 담고, 처음으로 형식에 맞지 않는 글자를 가리키는 ptr과 오류를 나타내는 ec를 함께 돌려줍니다. 숫자가 하나도 없으면 ec가 std::errc::invalid_argument, 범위를 넘으면 std::errc::result_out_of_range이고, 두 경우 모두 value는 바뀌지 않습니다. 그래서 ec가 비어 있고 ptr이 last와 같은지까지 보면, 그 줄이 정확히 숫자 하나였는지를 확인할 수 있습니다.

스트림의 >>와 달리 std::from_chars는 로캘(locale)의 영향을 받지 않고, 메모리를 할당하지 않으며, 예외도 던지지 않습니다. 대신 앞의 공백을 건너뛰지 않고 맨 앞의 + 부호도 받아들이지 않으므로, " 42"나 "+42"는 숫자로 읽히지 않습니다. 파일의 형식이 이런 표기를 허용한다면 바꾸기 전에 직접 다듬어야 하며, 자세한 규칙은 cppreference의 std::from_chars에 있습니다.

상태 플래그와 읽기가 멈춘 이유

스트림은 지금까지의 작업 결과를 상태 플래그로 기억합니다.

함수참이 되는 경우
good()어떤 오류 플래그도 설정되지 않음
eof()읽다가 파일 끝에 닿아 eofbit가 설정됨
fail()형식이 맞지 않는 등 작업이 실패해 failbit나 badbit가 설정됨
bad()장치나 버퍼에서 복구하기 어려운 오류가 나 badbit가 설정됨

clear()는 플래그를 지우고, rdstate()는 현재 플래그 전체를 돌려줍니다. 아래 그림처럼 읽기 루프는 읽기의 성공만으로 계속할지 정하고, 루프가 끝난 뒤에 bad, eof, fail의 순서로 멈춘 이유를 해석합니다. bad를 먼저 보는 것은 가장 심각한 오류이기 때문이고, eof일 때는 마지막 레코드가 완전했는지를 따로 확인합니다.

읽기 성공과 종료 이유를 분리하는 흐름

연결 성공 뒤 읽기 성공에서만 값 처리하고 실패하면 bad/eof/fail로 종료 이유를 판단한다. 형식/입력 위치 복구 후 재시도는 자동으로 추가하지 않는다.

파일 연결을 확인하고 읽기 자체의 성공으로 반복한 뒤 bad, eof, fail 상태를 순서대로 해석하며 필요한 경우 상태 비트와 입력 위치를 함께 복구하는 C++ 파일 읽기 흐름

C++ · stream state · flowchart

성공한 읽기만 처리하고, 멈춘 이유는 뒤에서 해석한다

while (in >> value)는 EOF를 미리 예측하지 않습니다. 추출 성공으로 반복을 결정하고, 루프가 끝난 뒤 상태 비트를 읽어 정상 종료와 형식 오류, 심각한 I/O 오류를 구분합니다.

읽기성공만처리하고종료이유를분리한다open→연결확인.아니요는열기오류로종료.예는열기오류를건너뛰어읽기성공검사.성공은값처리후읽기검사로돌아간다.실패는값처리를건너뛰어종료상태로간다.bad를우선보고심각오류;bad없을때eof는레코드경계검사;eof없는fail은형식오류복구다.복구후재시도자동간선은없고실제전략을본문으로정한다.9노드9간선의각성공/실패/루프관계를보존한다.파일 열기ifstream(path)연결됐나요?is_open()열기 오류경로·권한읽기 성공?in >> value값 처리성공한 레코드만종료 상태 확인bad → eof → fail심각한 I/O 오류badbit · 중단보고EOF 경계 검사완전 레코드인가?형식 오류·복구clear + 소비/위치아니요예예아니요badeoffailEOF만으로 레코드 완성 증명 안 됨
파일 읽기 성공과 종료 상태 해석 흐름 파일을 연 뒤 연결 여부를 확인한다. 연결되면 읽기가 성공하는 동안 값을 처리하고 다시 읽으며, 실패하면 bad를 먼저 보고 eof와 나머지 fail을 구분한다. 복구할 때는 clear와 입력 소비 또는 위치 조정을 함께 사용한다. no yes yes next record no fail bad eof 파일 열기 std::ifstream in(path) is_open()? 파일 연결 여부 열기 오류 경로 · 권한 · failbit 읽기 성공? in >> value 값 처리 성공한 레코드만 사용 종료 상태? bad → eof → fail 형식 오류 · 복구 clear() · 재시도 전략 상태와 위치를 함께 조정 심각한 I/O 오류 badbit · 중단/보고 EOF 경계 검사
  1. 연결을 먼저 확인

    is_open()으로 파일 연관 여부를 확인하고 열기 실패는 읽기 루프 전에 처리합니다.

  2. 읽기 결과로 반복

    while (in >> value)가 성공한 값만 본문에서 처리하고 다음 추출을 다시 시도합니다.

  3. bad()를 우선 확인

    하부 장치나 버퍼의 심각한 I/O 오류면 정상 EOF로 해석하지 않고 중단하거나 보고합니다.

  4. EOF와 나머지 실패를 구분

    정상적인 레코드 경계에서 끝났는지 확인합니다. 여러 필드 중간의 EOF라면 불완전 레코드일 수 있습니다.

  5. 복구는 상태와 입력을 함께

    clear()는 상태 비트만 지웁니다. 문제 토큰 소비나 파일 위치 조정은 의도에 맞게 별도로 수행합니다.

good

상태 비트가 없다

good()은 현재 비트만 보고하며 다음 I/O 성공까지 보장하지 않습니다.

eof

끝을 만나 eofbit가 섰다

읽기 시도가 끝에 닿은 결과이며 상황에 따라 failbit도 함께 설정될 수 있습니다.

fail

failbit 또는 badbit가 있다

타입 변환 실패 같은 복구 가능한 오류와 badbit를 모두 포함하는 질의입니다.

bad

복구하기 어려운 I/O 오류다

하부 장치나 스트림 버퍼 오류를 뜻하므로 단순히 clear()하고 재시도하지 않습니다.

eofbit와 failbit는 동시에 설정될 수 있습니다. 따라서 정상 EOF 판정에는 레코드가 완전했는지라는 입력 형식의 조건도 함께 들어갑니다. 다른 타입으로 같은 토큰을 다시 해석하는 복구라면 clear() 뒤 그 토큰을 그대로 추출할 수도 있습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목읽기 성공만 처리하고 종료 이유를 분리한다
관계·조건 설명open→연결 확인으로 이어진다. 아니요 분기는 열기 오류로 종료하고 예 분기는 열기 오류를 건너뛰어 읽기 성공을 검사한다. 성공은 값 처리 후 읽기 검사로 돌아간다. 실패는 값 처리를 건너뛰어 종료 상태로 간다. bad를 우선 보고 심각한 오류를 판단한다. bad가 없을 때 eof는 레코드 경계를 검사하고, eof가 없는 fail은 형식 오류 복구로 이어진다. 복구 후 재시도의 자동 간선은 없고 실제 전략을 본문에서 정한다. 9개 노드와 9개 간선의 각 성공/실패/루프 관계를 보존한다.
본문·주석끝을 만나 eofbit가 설정됐다
예의 값과 변화
관찰·상황값·보충 정보변화·이유
읽기 시도

연결 성공 뒤 추출 연산을 수행한다.

  • stream: 열림
  • 결과: 읽기 성공 여부 검사
  • 값 처리 조건은 연결 상태와 별도로 추출 결과에 달려 있다.
성공 처리

읽기가 성공한 값만 사용한다.

  • 값: 성공한 추출값
  • 실패값: 처리하지 않음
  • 실패 후 변수를 정상 입력처럼 소비하지 않는다.
실패 종료 판정

bad/eof/fail로 끝난 이유를 확인한다.

  • 확인: bad/eof/fail
  • 재시도: 자동으로 추가하지 않음
  • 입력 위치와 형식 복구 정책이 없는 clear만으로 해결을 약속하지 않는다.

읽기 성공과 종료 이유를 분리하는 흐름

연결 성공 뒤 읽기 성공에서만 값 처리하고 실패하면 bad/eof/fail로 종료 이유를 판단한다. 형식/입력 위치 복구 후 재시도는 자동으로 추가하지 않는다.

파일 연결을 확인하고 읽기 자체의 성공으로 반복한 뒤 bad, eof, fail 상태를 순서대로 해석하며 필요한 경우 상태 비트와 입력 위치를 함께 복구하는 C++ 파일 읽기 흐름

C++ · stream state · flowchart

성공한 읽기만 처리하고, 멈춘 이유는 뒤에서 해석한다

while (in >> value)는 EOF를 미리 예측하지 않습니다. 추출 성공으로 반복을 결정하고, 루프가 끝난 뒤 상태 비트를 읽어 정상 종료와 형식 오류, 심각한 I/O 오류를 구분합니다.

읽기성공만처리하고종료이유를분리한다open→연결확인.아니요는열기오류로종료.예는열기오류를건너뛰어읽기성공검사.성공은값처리후읽기검사로돌아간다.실패는값처리를건너뛰어종료상태로간다.bad를우선보고심각오류;bad없을때eof는레코드경계검사;eof없는fail은형식오류복구다.복구후재시도자동간선은없고실제전략을본문으로정한다.9노드9간선의각성공/실패/루프관계를보존한다.파일 열기ifstream(path)연결됐나요?is_open()열기 오류경로·권한읽기 성공?in >> value값 처리성공한 레코드만종료 상태 확인bad → eof → fail심각한 I/O 오류badbit · 중단보고EOF 경계 검사완전 레코드인가?형식 오류·복구clear + 소비/위치아니요예예아니요badeoffailEOF만으로 레코드 완성 증명 안 됨
파일 읽기 성공과 종료 상태 해석 흐름 파일을 연 뒤 연결 여부를 확인한다. 연결되면 읽기가 성공하는 동안 값을 처리하고 다시 읽으며, 실패하면 bad를 먼저 보고 eof와 나머지 fail을 구분한다. 복구할 때는 clear와 입력 소비 또는 위치 조정을 함께 사용한다. no yes yes next record no fail bad eof 파일 열기 std::ifstream in(path) is_open()? 파일 연결 여부 열기 오류 경로 · 권한 · failbit 읽기 성공? in >> value 값 처리 성공한 레코드만 사용 종료 상태? bad → eof → fail 형식 오류 · 복구 clear() · 재시도 전략 상태와 위치를 함께 조정 심각한 I/O 오류 badbit · 중단/보고 EOF 경계 검사
  1. 연결을 먼저 확인

    is_open()으로 파일 연관 여부를 확인하고 열기 실패는 읽기 루프 전에 처리합니다.

  2. 읽기 결과로 반복

    while (in >> value)가 성공한 값만 본문에서 처리하고 다음 추출을 다시 시도합니다.

  3. bad()를 우선 확인

    하부 장치나 버퍼의 심각한 I/O 오류면 정상 EOF로 해석하지 않고 중단하거나 보고합니다.

  4. EOF와 나머지 실패를 구분

    정상적인 레코드 경계에서 끝났는지 확인합니다. 여러 필드 중간의 EOF라면 불완전 레코드일 수 있습니다.

  5. 복구는 상태와 입력을 함께

    clear()는 상태 비트만 지웁니다. 문제 토큰 소비나 파일 위치 조정은 의도에 맞게 별도로 수행합니다.

good

상태 비트가 없다

good()은 현재 비트만 보고하며 다음 I/O 성공까지 보장하지 않습니다.

eof

끝을 만나 eofbit가 섰다

읽기 시도가 끝에 닿은 결과이며 상황에 따라 failbit도 함께 설정될 수 있습니다.

fail

failbit 또는 badbit가 있다

타입 변환 실패 같은 복구 가능한 오류와 badbit를 모두 포함하는 질의입니다.

bad

복구하기 어려운 I/O 오류다

하부 장치나 스트림 버퍼 오류를 뜻하므로 단순히 clear()하고 재시도하지 않습니다.

eofbit와 failbit는 동시에 설정될 수 있습니다. 따라서 정상 EOF 판정에는 레코드가 완전했는지라는 입력 형식의 조건도 함께 들어갑니다. 다른 타입으로 같은 토큰을 다시 해석하는 복구라면 clear() 뒤 그 토큰을 그대로 추출할 수도 있습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목읽기 성공만 처리하고 종료 이유를 분리한다
관계·조건 설명open→연결 확인으로 이어진다. 아니요 분기는 열기 오류로 종료하고 예 분기는 열기 오류를 건너뛰어 읽기 성공을 검사한다. 성공은 값 처리 후 읽기 검사로 돌아간다. 실패는 값 처리를 건너뛰어 종료 상태로 간다. bad를 우선 보고 심각한 오류를 판단한다. bad가 없을 때 eof는 레코드 경계를 검사하고, eof가 없는 fail은 형식 오류 복구로 이어진다. 복구 후 재시도의 자동 간선은 없고 실제 전략을 본문에서 정한다. 9개 노드와 9개 간선의 각 성공/실패/루프 관계를 보존한다.
본문·주석끝을 만나 eofbit가 설정됐다
예의 값과 변화
관찰·상황값·보충 정보변화·이유
읽기 시도

연결 성공 뒤 추출 연산을 수행한다.

  • stream: 열림
  • 결과: 읽기 성공 여부 검사
  • 값 처리 조건은 연결 상태와 별도로 추출 결과에 달려 있다.
성공 처리

읽기가 성공한 값만 사용한다.

  • 값: 성공한 추출값
  • 실패값: 처리하지 않음
  • 실패 후 변수를 정상 입력처럼 소비하지 않는다.
실패 종료 판정

bad/eof/fail로 끝난 이유를 확인한다.

  • 확인: bad/eof/fail
  • 재시도: 자동으로 추가하지 않음
  • 입력 위치와 형식 복구 정책이 없는 clear만으로 해결을 약속하지 않는다.

형식 오류로 실패한 뒤 다시 읽으려면 clear()만으로는 부족합니다. clear()는 플래그만 지울 뿐, 읽지 못한 글자는 입력에 그대로 남아 있습니다.

word.txt
abc 42
recover_format_error.cpp
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream in("word.txt");
    if (!in.is_open()) {
        std::cerr << "cannot open word.txt\n";
        return 1;
    }

    int number;
    if (!(in >> number)) {
        std::cout << "not a number\n";
        in.clear();
        std::string token;
        in >> token;
        std::cout << "skipped " << token << '\n';
    }
    if (in >> number) {
        std::cout << "number " << number << '\n';
    }
}
not a number
skipped abc
number 42

abc는 정수로 읽을 수 없어 failbit가 설정되고, clear()로 플래그를 지운 뒤 같은 abc를 문자열로 읽어 넘깁니다. 그다음에야 42를 정수로 읽을 수 있습니다. clear() 뒤에 abc를 넘기지 않고 다시 정수를 읽으면 같은 abc에서 또 실패하므로, 이런 코드를 루프 안에 두면 끝나지 않습니다.

정리

  • std::ifstream은 파일이 없으면 열기에 실패하므로, 읽기 전에 is_open()으로 확인합니다.
  • >>는 공백과 줄바꿈을 건너뛰고 값을 읽으며, while (in >> a >> b)는 모든 값을 읽은 경우에만 본문을 실행합니다.
  • std::getline은 한 줄을 읽고 줄바꿈은 버리며, 셋째 인자로 다른 구분 문자를 줄 수 있습니다.
  • >> 다음에 getline을 부르려면 ignore나 std::ws로 남은 줄바꿈을 처리하며, 둘은 받아들이는 형식이 다릅니다.
  • 루프가 끝난 뒤 bad, eof, fail로 멈춘 이유를 구분하고, eof만으로 마지막 레코드가 완전했다고 판단하지 않습니다.
  • clear()는 플래그만 지우므로, 다시 읽기 전에 문제가 된 입력을 소비해야 합니다.

연습 문제

1. 값이 빠진 마지막 레코드

numbers.txt의 둘째 줄을 200 Jane으로 바꾸면 read_tokens.cpp는 무엇을 출력하나요? 마지막 줄이 정상 종료 메시지라면 둘째 레코드가 완전했다고 볼 수 있나요?

해설 보기

첫 레코드는 정상적으로 출력되지만, 둘째 레코드는 value를 읽다가 파일 끝에 닿아 실패하므로 본문이 실행되지 않습니다. 이때 eofbit와 failbit가 함께 설정되어 stopped at end of file이 출력되지만, 둘째 레코드는 값이 빠진 불완전한 레코드입니다. 이를 오류로 다루려면 한 줄씩 읽어 필드 수를 검사해야 합니다.

2. 공백과 빈 줄이 있는 이름

user_data.txt의 내용이 100, Alice Wonderland(앞에 공백 두 개), 200, 빈 줄의 네 줄이라면 mix_token_and_line.cpp가 읽는 두 이름은 무엇인가요? std::ws를 쓰는 방식으로 바꾸면 어떻게 되나요?

해설 보기

ignore는 숫자가 있는 줄의 나머지만 버리므로, 첫 이름은 앞 공백 두 개가 남은 Alice Wonderland이고 둘째 이름은 빈 줄을 읽은 빈 문자열입니다. std::ws를 쓰면 첫 이름의 앞 공백이 사라지고, 둘째 레코드에서는 빈 줄까지 건너뛴 뒤 파일 끝에 닿아 이름을 읽지 못하므로 missing name for id 200을 출력하고 끝납니다.

3. 지우기만 한 상태

recover_format_error.cpp에서 in >> token;을 지우면 출력은 어떻게 되나요?

해설 보기

clear()로 플래그는 지워지지만 abc가 입력에 그대로 남아 있습니다. 그래서 skipped를 출력한 뒤의 in >> number가 다시 abc에서 실패해 number 42는 출력되지 않습니다. 이때 token은 빈 문자열이므로 출력은 not a number와 skipped 입니다.

다음 글 텍스트 파일 쓰기에서는 std::ofstream으로 파일에 글자를 쓰고, 열기 모드와 출력 버퍼, 쓰기 오류를 확인하는 방법을 다룹니다.