icon
8장 : 파일 입출력

스트림과 파일 입출력 개요

지금까지 우리는 키보드로부터 데이터를 입력받고 (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++ 표준 라이브러리(iostreamfstream 헤더)는 이러한 스트림을 처리하기 위한 클래스들을 제공합니다.

  • std::istream: 모든 입력 스트림의 기본 클래스입니다. std::cin이 이 클래스의 객체입니다.
  • std::ostream: 모든 출력 스트림의 기본 클래스입니다. std::cout, std::cerr, std::clog가 이 클래스의 객체입니다.
  • std::fstream: 파일 입출력을 위한 스트림 클래스입니다.

스트림 개념 덕분에 프로그래머는 데이터의 출처(키보드, 파일, 네트워크 등)나 목적지(화면, 파일, 네트워크 등)에 상관없이 동일한 인터페이스(<< 연산자, >> 연산자 등)를 사용하여 데이터를 처리할 수 있습니다.

예를 들어, 화면에 출력하는 것과 파일에 출력하는 것은 사용법이 거의 유사합니다.


파일 입출력의 기본 단계

C++에서 파일을 사용하여 데이터를 저장하고 불러오는 과정은 다음과 같은 기본 단계를 따릅니다.

  1. 헤더 파일 포함: 파일 입출력을 위해 <fstream> 헤더 파일을 포함해야 합니다. 이 헤더는 파일 스트림 클래스들(std::ifstream, std::ofstream, std::fstream)을 정의합니다.

    헤더 파일 포함
    #include <fstream> // 파일 입출력 클래스들을 위해
  2. 파일 스트림 객체 생성: 파일에서 읽거나 파일에 쓸 std::ifstream (입력 파일 스트림), std::ofstream (출력 파일 스트림), 또는 std::fstream (입력/출력 파일 스트림) 객체를 생성합니다.

    파일 스트림 객체 생성
    // 출력 파일 스트림 객체 (파일에 쓸 때)
    std::ofstream outputFile;
    
    // 입력 파일 스트림 객체 (파일에서 읽을 때)
    std::ifstream inputFile;
    
    // 입출력 파일 스트림 객체 (읽고 쓸 때)
    std::fstream ioFile;
  3. 파일 열기 (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은 기본 모드가 없으므로 항상 명시해야 합니다.
  4. 파일 열기 성공 여부 확인: 파일을 성공적으로 열었는지 항상 확인해야 합니다. 파일이 없거나 접근 권한이 없는 경우 열기 작업이 실패할 수 있습니다.

    파일 열기 성공 여부 확인
    if (outputFile.is_open()) { // 또는 if (outputFile)
        // 파일 열기 성공
    } else {
        // 파일 열기 실패
        std::cerr << "파일을 열 수 없습니다!" << std::endl;
    }
  5. 파일 읽기/쓰기: 스트림 연산자(<<, >>)나 멤버 함수(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); // 한 줄 전체 읽기
  6. 파일 닫기 (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 파일의 내용을 한 줄씩 읽어 콘솔에 출력합니다.