텍스트 파일 읽기
이제 구체적으로 텍스트 파일에서 데이터를 읽어오는 방법에 대해 더 깊이 있게 알아보겠습니다.
텍스트 파일은 사람이 읽을 수 있는 형태로 문자열 데이터가 저장된 파일이며, 가장 흔하게 접하는 파일 형식 중 하나입니다.
이 장에서는 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()
를 명시적으로 확인하여 예외적인 상황에 대응할 수 있습니다.