텍스트 파일 읽기
이번 절에서는 텍스트 파일에서 데이터를 읽어오는 방법을 더 구체적으로 다룹니다.
텍스트 파일은 사람이 읽을 수 있는 형태로 문자열 데이터가 저장된 파일이며, 가장 흔하게 접하는 파일 형식 중 하나입니다.
이 장에서는 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.fail()) { // .fail()은 .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.eof()) { // 파일의 끝에 도달했는지 확인
std::cout << "\n파일 끝에 도달했습니다. 모든 데이터를 읽었습니다." << std::endl;
} else if (inFile.fail()) { // 읽기 작업 중 오류가 발생했는지 확인
std::cerr << "\n오류: 데이터 읽기 중 문제가 발생했습니다." << std::endl;
}
inFile.close();
return 0;
}while (inFile >> id >> name >> value): 이 조건문은 데이터 추출(>>) 작업이 성공하는 동안true를 반환합니다. 데이터 추출에 실패하거나 파일의 끝에 도달하면false를 반환하여 루프를 종료합니다..eof(): 파일 끝(End Of File)에 도달했는지 여부를 나타내는 플래그를 확인합니다..fail(): 읽기 또는 쓰기 작업 중 비복구 가능한 오류가 발생했는지 확인합니다. (예: 잘못된 데이터 타입 읽기 시도)
줄 단위로 데이터 읽기 (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()의 혼용 시 주의사항:
>> 연산자는 공백을 건너뛰고 데이터를 읽지만, 개행 문자(\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');
std::getline(inFile, fullName);
std::cout << "ID: " << id << ", 이름: " << fullName << std::endl;
}
inFile.close();
return 0;
}std::numeric_limits<std::streamsize>::max(): 스트림의 최대 크기를 나타냅니다.\n: 개행 문자를 만날 때까지 무시합니다.
파일 상태 플래그 (File State Flags)
텍스트 파일 읽기는 열기 모드, 상태 플래그, 읽기·쓰기 단위, 닫기 책임으로 확인합니다.
파일 스트림 객체는 현재 파일의 상태를 나타내는 여러 플래그를 가집니다.
이를 통해 읽기/쓰기 작업의 성공 여부나 파일의 끝 도달 여부 등을 확인할 수 있습니다.
good(): 스트림이 정상 상태인지 (오류 없음, EOF 아님) 확인.true반환 시 모든 작업 가능.eof(): 파일 끝(End Of File)에 도달했는지 확인.fail(): 읽기/쓰기 작업 실패로 스트림이 실패 상태가 되었는지 확인 (예: 타입 불일치).bad(): 읽기/쓰기 작업 중 심각한 오류가 발생했는지 확인 (예: 하드웨어 오류, 파일 손상).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";
}
inFile.clear(); // 오류 플래그 초기화
return 1;
}
int num;
inFile >> num; // 숫자 읽기 시도
if (inFile.fail()) {
std::cout << "숫자를 읽는 데 실패했습니다.\n";
inFile.clear(); // 스트림 상태를 good()으로 재설정 (다시 읽기 시도 가능)
std::string s;
inFile >> s; // 다른 타입으로 다시 읽기 시도
std::cout << "다시 읽은 문자열: " << s << std::endl;
}
inFile.close();
return 0;
}파일 입출력 작업 후에는 항상 스트림의 상태를 확인하여 오류를 적절히 처리하는 것이 중요합니다.
특히 while(inFile >> data)와 같은 루프에서는 fail()이나 eof()를 명시적으로 확인하여 예외적인 상황에 대응할 수 있습니다.
읽기 루프를 작성할 때는 반복 조건과 루프 종료 후의 상태 해석을 분리해 두면 마지막 데이터와 오류를 안정적으로 구분할 수 있습니다.
텍스트 파일을 읽을 때는 토큰 단위 입력과 줄 단위 입력을 섞는 순간 남은 개행 문자와 상태 플래그를 함께 확인해야 합니다.