스트림과 파일 입출력 개요
지금까지 우리는 키보드로부터 데이터를 입력받고 (std::cin
), 화면에 데이터를 출력하는 (std::cout
) 콘솔 입출력을 주로 사용해 왔습니다.
하지만 프로그램의 데이터는 일회성으로 사용되는 경우가 많습니다.
프로그램이 종료되면 입력받은 데이터나 처리된 결과는 모두 사라집니다.
실제 응용 프로그램에서는 데이터를 영구적으로 저장하거나, 이전에 저장된 데이터를 불러와서 사용해야 할 필요가 있습니다.
이때 사용되는 것이 바로 파일 입출력(File I/O) 입니다.
이 장에서는 C++에서 파일 입출력을 처리하는 기본 개념인 스트림(Stream) 에 대해 알아보고, 파일 입출력의 개요를 살펴보겠습니다.
데이터 지속성 (Data Persistence)의 필요성
우리가 개발하는 프로그램은 종종 사용자 설정, 게임 진행 상황, 문서 내용, 이미지, 데이터베이스 정보 등 다양한 종류의 데이터를 다룹니다.
이러한 데이터가 프로그램 실행 중에만 존재하고 종료 시 사라진다면, 사용자는 매번 데이터를 다시 입력하거나 생성해야 하는 불편함을 겪게 됩니다.
데이터 지속성(Data Persistence) 은 프로그램이 종료되더라도 데이터가 영구적으로 저장되어 다음 프로그램 실행 시에도 접근할 수 있도록 하는 능력입니다.
파일은 데이터를 지속적으로 저장하는 가장 기본적인 방법 중 하나입니다.
스트림 (Stream) 개념 이해하기
C++의 입출력은 스트림(Stream) 이라는 추상적인 개념을 기반으로 합니다.
스트림은 데이터가 흐르는 통로(flow)를 의미합니다.
물이 흐르는 강물처럼 데이터도 한 곳에서 다른 곳으로 흘러간다고 생각할 수 있습니다.
- 출력 스트림 (Output Stream): 데이터가 프로그램에서 외부(화면, 파일 등)로 흘러나가는 통로입니다.
std::cout
이 대표적인 출력 스트림입니다. - 입력 스트림 (Input Stream): 데이터가 외부(키보드, 파일 등)에서 프로그램으로 흘러들어오는 통로입니다.
std::cin
이 대표적인 입력 스트림입니다.
C++ 표준 라이브러리(iostream
및 fstream
헤더)는 이러한 스트림을 처리하기 위한 클래스들을 제공합니다.
std::istream
: 모든 입력 스트림의 기본 클래스입니다.std::cin
이 이 클래스의 객체입니다.std::ostream
: 모든 출력 스트림의 기본 클래스입니다.std::cout
,std::cerr
,std::clog
가 이 클래스의 객체입니다.std::fstream
: 파일 입출력을 위한 스트림 클래스입니다.
스트림 개념 덕분에 프로그래머는 데이터의 출처(키보드, 파일, 네트워크 등)나 목적지(화면, 파일, 네트워크 등)에 상관없이 동일한 인터페이스(<<
연산자, >>
연산자 등)를 사용하여 데이터를 처리할 수 있습니다.
예를 들어, 화면에 출력하는 것과 파일에 출력하는 것은 사용법이 거의 유사합니다.
파일 입출력의 기본 단계
C++에서 파일을 사용하여 데이터를 저장하고 불러오는 과정은 다음과 같은 기본 단계를 따릅니다.
-
헤더 파일 포함: 파일 입출력을 위해
<fstream>
헤더 파일을 포함해야 합니다. 이 헤더는 파일 스트림 클래스들(std::ifstream
,std::ofstream
,std::fstream
)을 정의합니다.헤더 파일 포함 #include <fstream> // 파일 입출력 클래스들을 위해
-
파일 스트림 객체 생성: 파일에서 읽거나 파일에 쓸
std::ifstream
(입력 파일 스트림),std::ofstream
(출력 파일 스트림), 또는std::fstream
(입력/출력 파일 스트림) 객체를 생성합니다.파일 스트림 객체 생성 // 출력 파일 스트림 객체 (파일에 쓸 때) std::ofstream outputFile; // 입력 파일 스트림 객체 (파일에서 읽을 때) std::ifstream inputFile; // 입출력 파일 스트림 객체 (읽고 쓸 때) std::fstream ioFile;
-
파일 열기 (Open): 생성된 스트림 객체를 특정 파일과 연결합니다. 파일 이름과 (필요하다면) 파일 열기 모드를 지정합니다.
파일 열기 outputFile.open("example.txt"); // "example.txt" 파일을 쓰기 모드로 염 inputFile.open("data.txt"); // "data.txt" 파일을 읽기 모드로 염 ioFile.open("log.txt", std::ios::app); // "log.txt" 파일을 추가 모드로 염
파일을 열 때는 다음의
std::ios_base::openmode
플래그를 조합하여 열기 모드를 지정할 수 있습니다.모드 플래그 설명 기본값 std::ios::in
읽기 모드로 파일 열기 ifstream
의 기본std::ios::out
쓰기 모드로 파일 열기 (파일이 없으면 생성, 있으면 내용 지움) ofstream
의 기본std::ios::app
쓰기 모드, 파일 끝에 데이터를 추가 (append) std::ios::ate
파일 열 때 파일 포인터를 파일 끝으로 이동 (at end) std::ios::trunc
파일 열 때 파일 내용을 지움 (truncate) ofstream
의 기본에 포함std::ios::binary
이진 모드로 파일 열기 (텍스트 모드의 반대) ofstream
은 기본적으로std::ios::out | std::ios::trunc
모드로 열립니다.ifstream
은 기본적으로std::ios::in
모드로 열립니다.fstream
은 기본 모드가 없으므로 항상 명시해야 합니다.
-
파일 열기 성공 여부 확인: 파일을 성공적으로 열었는지 항상 확인해야 합니다. 파일이 없거나 접근 권한이 없는 경우 열기 작업이 실패할 수 있습니다.
파일 열기 성공 여부 확인 if (outputFile.is_open()) { // 또는 if (outputFile) // 파일 열기 성공 } else { // 파일 열기 실패 std::cerr << "파일을 열 수 없습니다!" << std::endl; }
-
파일 읽기/쓰기: 스트림 연산자(
<<
,>>
)나 멤버 함수(read
,write
,getline
등)를 사용하여 파일에 데이터를 쓰거나 파일에서 데이터를 읽습니다.파일 읽기/쓰기 // 파일에 쓰기 (출력 스트림) outputFile << "Hello, File I/O!" << std::endl; outputFile << 123 << " " << 45.67 << std::endl; // 파일에서 읽기 (입력 스트림) std::string line; int number; double value; inputFile >> number >> value; // 공백으로 구분된 데이터 읽기 std::getline(inputFile, line); // 한 줄 전체 읽기
-
파일 닫기 (Close): 파일 작업을 마친 후에는 반드시 파일을 닫아야 합니다. 이는 버퍼에 남아있는 데이터를 파일에 확실히 쓰고, 운영체제가 관리하는 파일 리소스를 해제하여 다른 프로그램이 해당 파일에 접근할 수 있도록 합니다.
파일 닫기 outputFile.close(); inputFile.close();
스트림 객체가 스코프를 벗어나 소멸될 때 (예: 함수 종료 시 지역 변수인 경우), 자동으로
close()
가 호출됩니다. 하지만 명시적으로close()
를 호출하는 것이 좋은 습관입니다.
간단한 파일 쓰기 예제
#include <iostream> // 콘솔 출력을 위해
#include <fstream> // 파일 입출력을 위해
#include <string> // std::string을 위해
int main() {
// 1. 출력 파일 스트림 객체 생성
std::ofstream outFile;
// 2. 파일 열기 (파일이 없으면 생성, 있으면 내용 지움)
outFile.open("my_output.txt"); // 기본적으로 std::ios::out 모드
// 3. 파일 열기 성공 여부 확인
if (outFile.is_open()) {
// 4. 파일에 데이터 쓰기
outFile << "이것은 파일에 쓰여질 첫 번째 줄입니다.\n";
outFile << "숫자: " << 12345 << std::endl;
outFile << "이름: " << "홍길동" << std::endl;
std::cout << "파일 'my_output.txt'에 성공적으로 기록했습니다." << std::endl;
// 5. 파일 닫기
outFile.close();
} else {
std::cerr << "오류: 'my_output.txt' 파일을 열 수 없습니다!" << std::endl;
}
return 0;
}
위 코드를 실행하면, 프로그램이 있는 디렉토리에 my_output.txt
라는 파일이 생성되거나 기존 파일의 내용이 지워지고 새로운 내용이 기록됩니다.
간단한 파일 읽기 예제
#include <iostream> // 콘솔 출력을 위해
#include <fstream> // 파일 입출력을 위해
#include <string> // std::string을 위해
int main() {
// 1. 입력 파일 스트림 객체 생성
std::ifstream inFile;
// 2. 파일 열기
inFile.open("my_output.txt"); // 위에서 생성한 파일을 읽기 모드로 엶
// 3. 파일 열기 성공 여부 확인
if (inFile.is_open()) {
// 4. 파일에서 데이터 읽기
std::string line;
// 파일 끝까지 한 줄씩 읽기
while (std::getline(inFile, line)) { // inFile에서 한 줄을 읽어 line 변수에 저장
std::cout << line << std::endl;
}
std::cout << "\n파일 'my_output.txt'에서 성공적으로 읽었습니다." << std::endl;
// 5. 파일 닫기
inFile.close();
} else {
std::cerr << "오류: 'my_output.txt' 파일을 열 수 없습니다! (파일이 없거나 권한 문제)" << std::endl;
}
return 0;
}
위 코드를 실행하면, my_output.txt
파일의 내용을 한 줄씩 읽어 콘솔에 출력합니다.