텍스트 파일 읽기
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을 사용하여 공백(스페이스, 탭, 개행 문자 등)으로 구분된 데이터를 읽듯이, 파일 스트림에서도 >> (추출) 연산자를 사용하여 공백으로 구분된 데이터를 읽을 수 있습니다.
이 연산자는 기본적으로 공백 문자를 건너뛰고 다음 데이터를 읽습니다.
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(입력스트림, 문자열변수, 구분문자); // 선택적으로 구분 문자 지정 가능 (기본값은 '\n')"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() 함수를 사용하여 남은 개행 문자를 버퍼에서 제거해야 합니다.
100
Alice Wonderland
200
Bob The Builder#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은 이름 전체가 된다. 다음 문자/추출 결과 상태를 나란히 추적한다.
C++ · formatted input → line input · sequence
읽기 단위가 바뀌면 다음 문자의 위치부터 확인한다
operator>>는 토큰을 변환하고 뒤따르는 구분 문자를 남길 수 있습니다. 곧바로 getline()을 호출할지, 현재 줄을 버리고 다음 줄부터 읽을지는 입력 형식이 결정합니다.
100\nAlice Wonderland\n
id = 100 다음 문자는 \n
getline(name)이 그 개행을 줄 끝으로 소비해 name == ""가 됩니다.
ignore(max, '\n') 뒤 다음 줄 읽기
현재 줄의 나머지와 개행을 버린 뒤 getline(name)이 Alice Wonderland를 읽습니다.
토큰을 변환한다
기본 skipws는 선행 공백을 건너뛰지만, 변환한 토큰 뒤의 구분 문자는 다음 입력에 남을 수 있습니다.
구분 문자까지 소비한다
한 줄의 내용을 문자열에 저장하고 구분 문자 자체는 버퍼에서 제거하되 결과 문자열에는 넣지 않습니다.
모든 선행 공백을 건너뛴다
빈 줄이나 들여쓰기를 보존해야 한다면 의미가 달라질 수 있으므로 입력 형식에 맞을 때만 사용합니다.
현재 줄의 나머지를 버린다
max()에 streamsize가 표현할 수 있는 최댓값을 쓰고 '\n'을 구분자로 주면 현재 줄 끝까지 폐기합니다.
핵심은 무조건 개행을 지우는 것이 아니라 다음 필드가 같은 줄인지 다음 줄인지 먼저 정하는 것입니다. 입력 단위가 바뀌는 지점에서는 버퍼의 다음 문자와 스트림 상태를 함께 확인합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 동일 입력에서 곧바로 줄 읽기와 현재 줄 폐기 후 읽기는 대안이다 |
| 관계·조건 설명 | 두 대안은 동일한 100 개행 Alice Wonderland 개행 입력 조건을 사용한다. 첫 경로는 getline→빈 문자열, 둘째는 ignore→getline→Alice Wonderland로 각각 1개/2개 간선이며 서로 연결하지 않는다. 첫 경로 뒤에 ignore를 연속 실행한다는 뜻이 아니다. 원래 가로 그림의 ALT 조건과 값 입력/반환은 본문 시퀀스를 참조한다. |
| 노드·연결선 라벨 | ID 100 · 다음 문자는 줄바꿈 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 숫자 추출 뒤 같은 입력 100 개행 Alice Wonderland에서 시작한다. |
|
|
| 바로 getline 첫 번째 독립 경로에서는 개행이 즉시 읽힌다. |
|
|
| ignore 뒤 getline 다른 사본에서는 개행까지 제거한 뒤 읽는다. |
|
|
토큰 입력 뒤 줄 입력이 보는 버퍼
같은 100 개행 Alice Wonderland 입력을 두 독립 경로에서 읽으면 바로 getline은 빈 이름, ignore 후 getline은 이름 전체가 된다. 다음 문자/추출 결과 상태를 나란히 추적한다.
C++ · formatted input → line input · sequence
읽기 단위가 바뀌면 다음 문자의 위치부터 확인한다
operator>>는 토큰을 변환하고 뒤따르는 구분 문자를 남길 수 있습니다. 곧바로 getline()을 호출할지, 현재 줄을 버리고 다음 줄부터 읽을지는 입력 형식이 결정합니다.
100\nAlice Wonderland\n
id = 100 다음 문자는 \n
getline(name)이 그 개행을 줄 끝으로 소비해 name == ""가 됩니다.
ignore(max, '\n') 뒤 다음 줄 읽기
현재 줄의 나머지와 개행을 버린 뒤 getline(name)이 Alice Wonderland를 읽습니다.
토큰을 변환한다
기본 skipws는 선행 공백을 건너뛰지만, 변환한 토큰 뒤의 구분 문자는 다음 입력에 남을 수 있습니다.
구분 문자까지 소비한다
한 줄의 내용을 문자열에 저장하고 구분 문자 자체는 버퍼에서 제거하되 결과 문자열에는 넣지 않습니다.
모든 선행 공백을 건너뛴다
빈 줄이나 들여쓰기를 보존해야 한다면 의미가 달라질 수 있으므로 입력 형식에 맞을 때만 사용합니다.
현재 줄의 나머지를 버린다
max()에 streamsize가 표현할 수 있는 최댓값을 쓰고 '\n'을 구분자로 주면 현재 줄 끝까지 폐기합니다.
핵심은 무조건 개행을 지우는 것이 아니라 다음 필드가 같은 줄인지 다음 줄인지 먼저 정하는 것입니다. 입력 단위가 바뀌는 지점에서는 버퍼의 다음 문자와 스트림 상태를 함께 확인합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 동일 입력에서 곧바로 줄 읽기와 현재 줄 폐기 후 읽기는 대안이다 |
| 관계·조건 설명 | 두 대안은 동일한 100 개행 Alice Wonderland 개행 입력 조건을 사용한다. 첫 경로는 getline→빈 문자열, 둘째는 ignore→getline→Alice Wonderland로 각각 1개/2개 간선이며 서로 연결하지 않는다. 첫 경로 뒤에 ignore를 연속 실행한다는 뜻이 아니다. 원래 가로 그림의 ALT 조건과 값 입력/반환은 본문 시퀀스를 참조한다. |
| 노드·연결선 라벨 | ID 100 · 다음 문자는 줄바꿈 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 숫자 추출 뒤 같은 입력 100 개행 Alice Wonderland에서 시작한다. |
|
|
| 바로 getline 첫 번째 독립 경로에서는 개행이 즉시 읽힌다. |
|
|
| ignore 뒤 getline 다른 사본에서는 개행까지 제거한 뒤 읽는다. |
|
|
파일 상태 플래그 (File State Flags)
텍스트 파일 읽기는 열기 모드, 상태 플래그, 읽기·쓰기 단위, 닫기 책임으로 확인합니다.
읽기 성공과 종료 이유를 분리하는 흐름
연결 성공 뒤 읽기 성공에서만 값 처리하고 실패하면 bad/eof/fail로 종료 이유를 판단한다. 형식/입력 위치 복구 후 재시도는 자동으로 추가하지 않는다.
C++ · stream state · flowchart
성공한 읽기만 처리하고, 멈춘 이유는 뒤에서 해석한다
while (in >> value)는 EOF를 미리 예측하지 않습니다. 추출 성공으로 반복을 결정하고, 루프가 끝난 뒤 상태 비트를 읽어 정상 종료와 형식 오류, 심각한 I/O 오류를 구분합니다.
- 연결을 먼저 확인
is_open()으로 파일 연관 여부를 확인하고 열기 실패는 읽기 루프 전에 처리합니다. - 읽기 결과로 반복
while (in >> value)가 성공한 값만 본문에서 처리하고 다음 추출을 다시 시도합니다. bad()를 우선 확인하부 장치나 버퍼의 심각한 I/O 오류면 정상 EOF로 해석하지 않고 중단하거나 보고합니다.
- EOF와 나머지 실패를 구분
정상적인 레코드 경계에서 끝났는지 확인합니다. 여러 필드 중간의 EOF라면 불완전 레코드일 수 있습니다.
- 복구는 상태와 입력을 함께
clear()는 상태 비트만 지웁니다. 문제 토큰 소비나 파일 위치 조정은 의도에 맞게 별도로 수행합니다.
상태 비트가 없다
good()은 현재 비트만 보고하며 다음 I/O 성공까지 보장하지 않습니다.
끝을 만나 eofbit가 섰다
읽기 시도가 끝에 닿은 결과이며 상황에 따라 failbit도 함께 설정될 수 있습니다.
failbit 또는 badbit가 있다
타입 변환 실패 같은 복구 가능한 오류와 badbit를 모두 포함하는 질의입니다.
복구하기 어려운 I/O 오류다
하부 장치나 스트림 버퍼 오류를 뜻하므로 단순히 clear()하고 재시도하지 않습니다.
eofbit와 failbit는 동시에 설정될 수 있습니다. 따라서 정상 EOF 판정에는 레코드가 완전했는지라는 입력 형식의 조건도 함께 들어갑니다. 다른 타입으로 같은 토큰을 다시 해석하는 복구라면 clear() 뒤 그 토큰을 그대로 추출할 수도 있습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 읽기 성공만 처리하고 종료 이유를 분리한다 |
| 관계·조건 설명 | open→연결 확인으로 이어진다. 아니요 분기는 열기 오류로 종료하고 예 분기는 열기 오류를 건너뛰어 읽기 성공을 검사한다. 성공은 값 처리 후 읽기 검사로 돌아간다. 실패는 값 처리를 건너뛰어 종료 상태로 간다. bad를 우선 보고 심각한 오류를 판단한다. bad가 없을 때 eof는 레코드 경계를 검사하고, eof가 없는 fail은 형식 오류 복구로 이어진다. 복구 후 재시도의 자동 간선은 없고 실제 전략을 본문에서 정한다. 9개 노드와 9개 간선의 각 성공/실패/루프 관계를 보존한다. |
| 본문·주석 | 끝을 만나 eofbit가 설정됐다 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 읽기 시도 연결 성공 뒤 추출 연산을 수행한다. |
|
|
| 성공 처리 읽기가 성공한 값만 사용한다. |
|
|
| 실패 종료 판정 bad/eof/fail로 끝난 이유를 확인한다. |
|
|
읽기 성공과 종료 이유를 분리하는 흐름
연결 성공 뒤 읽기 성공에서만 값 처리하고 실패하면 bad/eof/fail로 종료 이유를 판단한다. 형식/입력 위치 복구 후 재시도는 자동으로 추가하지 않는다.
C++ · stream state · flowchart
성공한 읽기만 처리하고, 멈춘 이유는 뒤에서 해석한다
while (in >> value)는 EOF를 미리 예측하지 않습니다. 추출 성공으로 반복을 결정하고, 루프가 끝난 뒤 상태 비트를 읽어 정상 종료와 형식 오류, 심각한 I/O 오류를 구분합니다.
- 연결을 먼저 확인
is_open()으로 파일 연관 여부를 확인하고 열기 실패는 읽기 루프 전에 처리합니다. - 읽기 결과로 반복
while (in >> value)가 성공한 값만 본문에서 처리하고 다음 추출을 다시 시도합니다. bad()를 우선 확인하부 장치나 버퍼의 심각한 I/O 오류면 정상 EOF로 해석하지 않고 중단하거나 보고합니다.
- EOF와 나머지 실패를 구분
정상적인 레코드 경계에서 끝났는지 확인합니다. 여러 필드 중간의 EOF라면 불완전 레코드일 수 있습니다.
- 복구는 상태와 입력을 함께
clear()는 상태 비트만 지웁니다. 문제 토큰 소비나 파일 위치 조정은 의도에 맞게 별도로 수행합니다.
상태 비트가 없다
good()은 현재 비트만 보고하며 다음 I/O 성공까지 보장하지 않습니다.
끝을 만나 eofbit가 섰다
읽기 시도가 끝에 닿은 결과이며 상황에 따라 failbit도 함께 설정될 수 있습니다.
failbit 또는 badbit가 있다
타입 변환 실패 같은 복구 가능한 오류와 badbit를 모두 포함하는 질의입니다.
복구하기 어려운 I/O 오류다
하부 장치나 스트림 버퍼 오류를 뜻하므로 단순히 clear()하고 재시도하지 않습니다.
eofbit와 failbit는 동시에 설정될 수 있습니다. 따라서 정상 EOF 판정에는 레코드가 완전했는지라는 입력 형식의 조건도 함께 들어갑니다. 다른 타입으로 같은 토큰을 다시 해석하는 복구라면 clear() 뒤 그 토큰을 그대로 추출할 수도 있습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 읽기 성공만 처리하고 종료 이유를 분리한다 |
| 관계·조건 설명 | open→연결 확인으로 이어진다. 아니요 분기는 열기 오류로 종료하고 예 분기는 열기 오류를 건너뛰어 읽기 성공을 검사한다. 성공은 값 처리 후 읽기 검사로 돌아간다. 실패는 값 처리를 건너뛰어 종료 상태로 간다. bad를 우선 보고 심각한 오류를 판단한다. bad가 없을 때 eof는 레코드 경계를 검사하고, eof가 없는 fail은 형식 오류 복구로 이어진다. 복구 후 재시도의 자동 간선은 없고 실제 전략을 본문에서 정한다. 9개 노드와 9개 간선의 각 성공/실패/루프 관계를 보존한다. |
| 본문·주석 | 끝을 만나 eofbit가 설정됐다 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 읽기 시도 연결 성공 뒤 추출 연산을 수행한다. |
|
|
| 성공 처리 읽기가 성공한 값만 사용한다. |
|
|
| 실패 종료 판정 bad/eof/fail로 끝난 이유를 확인한다. |
|
|
파일 스트림 객체는 현재 파일의 상태를 나타내는 여러 플래그를 가집니다.
이를 통해 읽기/쓰기 작업의 성공 여부나 파일의 끝 도달 여부 등을 확인할 수 있습니다.
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 가능한 스트림이라면 의도한 위치로 옮겨야 합니다. 다른 타입으로 같은 토큰을 해석하려는 경우에는 상태 비트만 지운 뒤 그대로 다시 추출할 수도 있습니다.
파일 내용과 읽기 단위를 맞추어 풀기
앞의 두 파일 형식을 구분해 독립적으로 판단합니다. 아래는 새 입력 조건이며, 기존 정상 파일을 실습 폴더에 복사해 둔 뒤 바꿉니다.
- numbers.txt의 둘째 레코드를
200 Jane으로 끝내 value를 생략했습니다. 세 필드를 모두 읽는 while 본문은 이 레코드를 출력하나요? 종료 후 eof가 참이면 레코드가 완전했다고 볼 수 있나요? - 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라서 이름을 얻지 못하고 오류 분기에 들어갑니다. 공백 제거는 단순한 문법 수정이 아니라 허용하는 파일 형식을 바꾸는 선택입니다.