본문 바로가기

텍스트 파일 읽기

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

이번 절에서는 텍스트 파일에서 데이터를 읽어오는 방법을 더 구체적으로 다룹니다.

텍스트 파일은 사람이 읽을 수 있는 형태로 문자열 데이터가 저장된 파일이며, 가장 흔하게 접하는 파일 형식 중 하나입니다.

이 장에서는 std::ifstream을 사용하여 텍스트 파일에서 문자열, 숫자, 공백이 포함된 줄 단위 데이터를 읽어오는 방법을 정리합니다.


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

텍스트 파일을 읽기 위해서는 std::ifstream 클래스의 객체를 사용합니다.

ifstream은 Input File Stream의 약자입니다.

파일을 여는 방법은 지난 장에서 다루었듯이 open() 멤버 함수를 사용하거나, ifstream 객체를 생성할 때 파일 이름을 인자로 전달하여 바로 열 수 있습니다.

텍스트 파일 열기 예제
#include <fstream> // std::ifstream을 위해
#include <iostream> // std::cout, std::endl을 위해

int main() {
    // 방법 1: 생성과 동시에 파일 열기
    std::ifstream inFile1("data.txt");
    if (!inFile1.is_open()) {
        std::cerr << "data.txt를 열 수 없습니다!\n";
    } else {
        // 파일 처리...
        inFile1.close();
    }

    // 방법 2: 객체 생성 후 open() 호출
    std::ifstream inFile2;
    inFile2.open("another_data.txt");
    if (!inFile2.is_open()) { // 파일 연결 여부 확인
        std::cerr << "another_data.txt를 열 수 없습니다!\n";
    } else {
        // 파일 처리...
        inFile2.close();
    }

    return 0;
}

std::ifstream은 기본적으로 std::ios::in 모드로 파일을 엽니다.

이 모드는 파일이 존재하지 않으면 파일을 생성하지 않고 열기에 실패합니다.


공백으로 구분된 데이터 읽기 (>> 연산자)

콘솔에서 std::cin을 사용하여 공백(스페이스, 탭, 개행 문자 등)으로 구분된 데이터를 읽듯이, 파일 스트림에서도 >> (추출) 연산자를 사용하여 공백으로 구분된 데이터를 읽을 수 있습니다.

이 연산자는 기본적으로 공백 문자를 건너뛰고 다음 데이터를 읽습니다.

numbers.txt
100 John 3.14
200 Jane 2.718
공백으로 구분된 데이터 읽기 예제
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inFile("numbers.txt");

    if (!inFile.is_open()) {
        std::cerr << "오류: numbers.txt 파일을 열 수 없습니다!\n";
        return 1;
    }

    int id;
    std::string name;
    double value;

    // 파일 끝(EOF)에 도달할 때까지 데이터 읽기
    while (inFile >> id >> name >> value) { // inFile에서 데이터를 읽어 id, name, value에 저장
        std::cout << "ID: " << id << ", 이름: " << name << ", 값: " << value << std::endl;
    }

    if (inFile.bad()) { // 하부 I/O에서 심각한 오류가 발생했는지 확인
        std::cerr << "\n오류: 파일 장치나 버퍼에서 I/O 문제가 발생했습니다." << std::endl;
    } else if (inFile.eof()) { // eofbit와 failbit가 함께 설정될 수도 있음
        std::cout << "\nEOF에서 읽기가 종료되었습니다." << std::endl;
    } else if (inFile.fail()) { // 형식 변환 등 읽기 실패 확인
        std::cerr << "\n오류: 데이터 읽기 중 문제가 발생했습니다." << std::endl;
    }

    inFile.close();
    return 0;
}
  • while (inFile >> id >> name >> value): 데이터 추출(>>)은 스트림을 반환하고, 조건식은 그 스트림에 failbit나 badbit가 없는지를 평가합니다. 레코드 추출에 실패하면 루프를 종료합니다.
  • .eof(): 파일 끝(End Of File)에 도달했는지 여부를 나타내는 플래그를 확인합니다.
  • .fail(): failbit 또는 badbit가 설정되었는지 확인합니다. 타입 불일치 같은 형식 오류는 상태를 지우고 문제 입력을 처리하면 복구할 수 있지만, badbit는 더 심각한 I/O 오류를 뜻합니다.

여러 필드를 한 번에 추출하는 도중 EOF에 도달하면 eofbit와 failbit가 함께 설정될 수 있습니다. 정상적인 파일 끝과 불완전한 마지막 레코드를 구분해야 한다면 필드별 성공 여부나 레코드 형식을 별도로 검증합니다.


줄 단위로 데이터 읽기 (std::getline())

때로는 데이터가 공백으로 구분되지 않고, 한 줄 전체가 하나의 의미 있는 덩어리인 경우가 있습니다.

문장이나 주소록의 한 항목이 그 예입니다.

이때는 std::getline() 함수를 사용하여 개행 문자(\n)를 기준으로 한 줄 전체를 읽어올 수 있습니다.

std::getline() 사용 형식
std::getline(입력스트림, 문자열변수);
std::getline(입력스트림, 문자열변수, 구분문자); // 선택적으로 구분 문자 지정 가능 (기본값은 '\n')
quotes.txt
"Imagination is more important than knowledge." - Albert Einstein
"The only way to do great work is to love what you do." - Steve Jobs
Life is what happens when you're busy making other plans. - John Lennon
줄 단위로 데이터 읽기 예제
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inFile("quotes.txt");

    if (!inFile.is_open()) {
        std::cerr << "오류: quotes.txt 파일을 열 수 없습니다!\n";
        return 1;
    }

    std::string line;
    int lineNumber = 1;

    // 파일 끝까지 한 줄씩 읽기
    while (std::getline(inFile, line)) {
        std::cout << "줄 " << lineNumber++ << ": " << line << std::endl;
    }

    inFile.close();
    return 0;
}

std::getline()은 개행 문자를 읽고 버퍼에서 제거하지만, 반환되는 line 문자열에는 개행 문자가 포함되지 않습니다.

>> 연산자와 getline()의 혼용 시 주의사항: >> 연산자는 기본 설정에서 선행 공백을 건너뛰고 토큰을 변환하지만, 토큰 뒤의 구분 공백은 남길 수 있습니다. 이 예제에서는 ID 뒤의 개행 문자(\n)가 다음 입력의 시작점에 남습니다.

만약 >> 연산자 다음에 getline()을 호출하면, getline()은 남아 있는 개행 문자를 빈 줄로 해석하여 즉시 반환해 버리는 문제가 발생할 수 있습니다.

이를 해결하려면 >> 연산자 후에 std::ws (Whitespace manipulator)를 사용하거나, ignore() 함수를 사용하여 남은 개행 문자를 버퍼에서 제거해야 합니다.

user_data.txt
100
Alice Wonderland
200
Bob The Builder
>>와 getline() 혼용 시 문제 해결 예제
#include <iostream>
#include <fstream>
#include <string>
#include <limits> // std::numeric_limits

int main() {
    std::ifstream inFile("user_data.txt");

    if (!inFile.is_open()) {
        std::cerr << "오류: user_data.txt 파일을 열 수 없습니다!\n";
        return 1;
    }

    int id;
    std::string fullName;

    while (inFile >> id) { // ID(숫자)를 읽고, 개행 문자는 버퍼에 남음
        // 해결책 1: std::ws (Whitespace manipulator) 사용
        // inFile >> std::ws; // 남아있는 모든 공백(개행 포함)을 건너뛴다.
        // std::getline(inFile, fullName);

        // 해결책 2: ignore() 함수 사용
        // 현재 줄의 나머지(개행 문자 포함)를 무시
        inFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (!std::getline(inFile, fullName)) {
            std::cerr << "오류: ID " << id << "의 이름 줄을 읽지 못했습니다.\n";
            return 1;
        }

        std::cout << "ID: " << id << ", 이름: " << fullName << std::endl;
    }

    inFile.close();
    return 0;
}

이 예제는 ID와 다음 줄의 이름을 모두 읽은 뒤에만 한 레코드를 출력합니다. ID만 읽고 이름 줄을 읽지 못했다면 오류를 보고하고 끝냅니다. 현재 ignore() 경로는 빈 이름 줄을 그대로 허용하며, std::ws로 바꾸면 이런 줄까지 건너뛸 수 있으므로 두 방식을 같은 정책으로 보지 마세요.

마지막 이름 줄에 개행이 없어도 이름의 문자를 읽었다면 getline()은 성공할 수 있습니다. 이때 eofbit만 설정될 수 있으므로, EOF 여부만 보지 않고 실제 읽기 결과로 레코드 완성을 판단합니다.

  • std::numeric_limits<std::streamsize>::max(): std::streamsize가 표현할 수 있는 최댓값입니다.
  • \n: 개행 문자를 만날 때까지 무시합니다.

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

같은 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이 공백을 포함한 이름을 받는다.

파일 상태 플래그 (File State Flags)

텍스트 파일 읽기는 열기 모드, 상태 플래그, 읽기·쓰기 단위, 닫기 책임으로 확인합니다.

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

연결 성공 뒤 읽기 성공에서만 값 처리하고 실패하면 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만으로 해결을 약속하지 않는다.

파일 스트림 객체는 현재 파일의 상태를 나타내는 여러 플래그를 가집니다.

이를 통해 읽기/쓰기 작업의 성공 여부나 파일의 끝 도달 여부 등을 확인할 수 있습니다.

  • good(): 오류 상태 비트가 하나도 설정되지 않았는지 확인합니다. 이후 작업의 성공까지 보장하지는 않습니다.
  • eof(): 파일 끝(End Of File)에 도달했는지 확인.
  • fail(): failbit 또는 badbit가 설정되었는지 확인합니다. 예를 들어 타입 불일치는 보통 failbit를 설정합니다.
  • bad(): 하부 장치나 스트림 버퍼에서 복구하기 어려운 I/O 오류가 발생했는지 확인합니다.
  • clear(): 상태 비트를 지웁니다. 문제 입력을 버리거나 파일 위치를 되돌리지는 않으므로 재시도 전략에 맞는 소비 또는 위치 조정이 따로 필요합니다.
  • rdstate(): 현재 스트림의 상태 플래그들을 비트마스크로 반환.
파일 상태 플래그 예제
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inFile("non_existent_file.txt"); // 존재하지 않는 파일

    if (!inFile.is_open()) {
        std::cerr << "파일을 열 수 없습니다. ";
        if (inFile.fail()) { // 열기 실패 시 fail()이 true
            std::cerr << "fail() 상태입니다.\n";
        }
        return 1;
    }

    int num;
    inFile >> num; // 숫자 읽기 시도

    if (inFile.fail()) {
        std::cout << "숫자를 읽는 데 실패했습니다.\n";
        inFile.clear(); // 상태 비트만 지우며, 실패한 토큰은 입력에 남아 있음
        std::string s;
        if (inFile >> s) { // 같은 토큰을 문자열로 읽는 의도적인 복구
            std::cout << "다시 읽은 문자열: " << s << std::endl;
        }
    }

    inFile.close();
    return 0;
}

파일 입출력 작업 후에는 항상 스트림의 상태를 확인하여 오류를 적절히 처리하는 것이 중요합니다.

특히 while(inFile >> data)와 같은 루프에서는 fail()이나 eof()를 명시적으로 확인하여 예외적인 상황에 대응할 수 있습니다.

읽기 루프를 작성할 때는 반복 조건과 루프 종료 후의 상태 해석을 분리해 두면 마지막 데이터와 오류를 안정적으로 구분할 수 있습니다.

clear() 뒤 같은 타입을 다시 읽으려면 문제 토큰을 ignore() 등으로 소비하거나, seek 가능한 스트림이라면 의도한 위치로 옮겨야 합니다. 다른 타입으로 같은 토큰을 해석하려는 경우에는 상태 비트만 지운 뒤 그대로 다시 추출할 수도 있습니다.

파일 내용과 읽기 단위를 맞추어 풀기

앞의 두 파일 형식을 구분해 독립적으로 판단합니다. 아래는 새 입력 조건이며, 기존 정상 파일을 실습 폴더에 복사해 둔 뒤 바꿉니다.

  1. numbers.txt의 둘째 레코드를 200 Jane으로 끝내 value를 생략했습니다. 세 필드를 모두 읽는 while 본문은 이 레코드를 출력하나요? 종료 후 eof가 참이면 레코드가 완전했다고 볼 수 있나요?
  2. user_data.txt를 100\n Alice Wonderland\n200\n\n의 문자 순서로 만들었습니다. 현재 ignore 경로에서 두 이름 문자열은 무엇인가요? 주석 처리된 std::ws 경로로 대신 읽으면 무엇이 달라지나요? 여기서 \n 표기는 파일의 실제 줄바꿈을 뜻합니다.
추출 성공과 형식 정책의 풀이

첫 조건은 id와 name을 읽어도 value 추출에 실패하므로 둘째 레코드의 while 본문을 실행하지 않습니다. eofbit와 failbit가 함께 설정될 수 있고, EOF 메시지는 완전한 레코드라는 증거가 아닙니다. 필드별 검증이 필요하다는 본문의 경계를 적용합니다.

둘째 조건에서 ignore는 ID가 있는 줄의 나머지만 버립니다. 따라서 첫 이름은 앞 공백 두 개를 보존한 Alice Wonderland, 둘째는 빈 문자열입니다. 빈 줄의 구분 문자를 실제로 추출했으므로 현재 코드는 빈 이름 레코드를 허용합니다. std::ws는 첫 이름의 선행 공백을 없애고, 둘째 ID 뒤의 빈 줄도 건너뜁니다. 그 뒤 EOF라서 이름을 얻지 못하고 오류 분기에 들어갑니다. 공백 제거는 단순한 문법 수정이 아니라 허용하는 파일 형식을 바꾸는 선택입니다.