본문 바로가기

스트림과 파일 입출력 개요

스트림을 통한 데이터 흐름과 파일의 지속성을 이해하고 열기·읽기·쓰기·상태 확인·닫기의 기본 절차를 익힙니다.

지금까지의 프로그램은 키보드로 입력받고 화면에 출력했습니다. 이런 데이터는 프로그램이 끝나면 메모리와 함께 사라지므로, 설정이나 저장한 게임, 작성한 문서처럼 다음 실행에도 남아야 하는 데이터는 파일에 기록해야 합니다. 프로그램이 끝나도 데이터가 남는 성질을 지속성(persistence)이라고 하며, 파일은 그 가장 기본적인 방법입니다.

이 장에서는 C++의 파일 입출력을 다룹니다. 이 글은 그 바탕인 스트림(stream)의 구조와, 파일을 열고 읽고 쓰고 닫는 기본 단계, 그리고 각 단계에서 확인할 상태를 정리합니다. 텍스트 파일을 읽고 쓰는 자세한 방법과 바이너리 파일은 이어지는 글에서 봅니다.

프로그램, 형식화 스트림, 스트림 버퍼, 파일과 외부 장치 사이의 입력·출력 방향을 보여 주는 C++ 스트림 구조

C++ · stream architecture · persistence

스트림 API는 버퍼를 거쳐 외부 자원과 값을 주고받는다

형식화는 스트림이, 실제 전송은 연결된 std::streambuf가 맡습니다. 파일은 표준 구성이고 네트워크는 별도 라이브러리나 플랫폼의 버퍼와 어댑터가 필요합니다.

입력과출력은서로반대인두별도경로desktop과같은두구획각4노드3간선이다.입력은외부→버퍼→istream→프로그램,출력은프로그램→ostream→버퍼→외부다.두구획을한실행순서로연결하지않는다.네트워크는별도버퍼구현이필요하고파일처리는기본표준구성이다.입력: 외부 → 프로그램외부 원천키보드·파일버퍼·어댑터filebuf / 외부 구현std::istream>> / getline프로그램 값메모리에서 사용출력: 프로그램 → 외부프로그램 값형식화할 데이터std::ostream<< / write버퍼·어댑터filebuf / 외부 구현외부 목적지화면·지속 파일
C++ 입력과 출력 스트림의 계층 구조 입력 행에서는 외부 원천에서 버퍼와 istream을 거쳐 프로그램으로 값이 들어오고, 출력 행에서는 프로그램에서 ostream과 버퍼를 거쳐 파일이나 외부 장치로 값이 나간다. INPUT · 외부 → 프로그램 외부 원천 키보드 · 파일 버퍼·어댑터 filebuf / external std::istream >> · getline 프로그램 값 메모리에서 사용 OUTPUT · 프로그램 → 외부 프로그램 값 형식화할 데이터 std::ostream << · write 버퍼·어댑터 filebuf / external 외부 목적지 화면 · 지속 파일
input

외부에서 프로그램으로

원천 → 버퍼·어댑터 → std::istream → 프로그램 순서로 읽습니다. 연결 상태와 읽기 결과를 각각 확인합니다.

output

프로그램에서 외부로

프로그램 → std::ostream → 버퍼·어댑터 → 파일 순서로 씁니다. 모드와 쓰기, 플러시 상태를 확인합니다.

both

양방향 파일 스트림

std::fstream은 std::iostream 계열 API와 파일 버퍼를 결합합니다. 방향과 모드, 읽기와 쓰기 위치를 함께 관리합니다.

network

네트워크는 별도 어댑터

표준 iostream에는 네트워크 스트림이 없습니다. 라이브러리나 플랫폼의 std::streambuf 구현이 필요합니다.

파일 스트림 클래스의 방향과 버퍼 연결
클래스형식화 API데이터 방향확인 책임
std::ifstreamstd::istream파일 → 프로그램연결 상태와 읽기 결과
std::ofstreamstd::ostream프로그램 → 파일모드와 쓰기·플러시 상태
std::fstreamstd::iostream파일 ↔ 프로그램방향·모드와 읽기/쓰기 위치

파일은 실행이 끝나도 값을 남기는 지속 장치입니다. 클래스를 고른 뒤에는 경로·모드·상태와 작업 결과를 확인합니다. 같은 API를 다른 자원에 재사용하려면 그 자원과 통신하는 버퍼나 어댑터가 있어야 합니다.

스트림과 버퍼

C++의 입출력은 스트림이라는 통로를 거칩니다. 위 그림처럼 입력 스트림은 키보드나 파일에서 프로그램으로 값을 가져오고, 출력 스트림은 프로그램의 값을 화면이나 파일로 내보냅니다. 지금까지 쓴 std::cin은 입력 스트림인 std::istream의 객체이고, std::cout, std::cerr, std::clog는 출력 스트림인 std::ostream의 객체입니다.

스트림은 <<와 >>로 값을 글자로 바꾸거나 글자를 값으로 바꾸는 형식화(formatting)를 맡고, 실제로 외부와 바이트를 주고받는 일은 스트림에 연결된 버퍼인 std::streambuf가 맡습니다. 파일 스트림은 파일과 연결된 버퍼를 쓰므로, 콘솔에 쓰던 <<, >>, std::getline을 파일에도 똑같이 쓸 수 있습니다. 네트워크 연결도 이론상 같은 인터페이스로 다룰 수 있지만, 표준 라이브러리는 그런 버퍼를 제공하지 않아 별도의 라이브러리가 필요합니다.

파일 스트림은 <fstream> 헤더에 있는 세 클래스입니다.

클래스방향기본 열기 모드
std::ifstream파일에서 읽기in
std::ofstream파일에 쓰기out, 기존 내용을 지움
std::fstream읽기와 쓰기in 과 out

더 깊이: 표준 스트림끼리 묶여 있는 방식

콘솔의 표준 스트림에는 파일 스트림에 없는 연결이 몇 가지 있습니다. 먼저 std::cin은 std::cout에 묶여(tie) 있어서, std::cin으로 읽기 전에 std::cout에 쌓인 출력이 있으면 그것을 먼저 비웁니다. 그래서 std::cout << "Enter n: "; 뒤에 std::endl 없이 std::cin >> n;을 해도 안내문이 먼저 화면에 나타납니다. std::cerr도 std::cout에 묶여 있고 unitbuf 형식 플래그가 켜져 있어서, 쓸 때마다 앞의 std::cout 출력을 먼저 비운 뒤 자기 출력도 곧바로 내보냅니다.

또 기본적으로 여덟 개의 표준 C++ 스트림은 C의 stdin, stdout, stderr와 동기화되어 있습니다. 동기화된 C++ 스트림은 사실상 자기 버퍼 없이 C 스트림의 버퍼에 바로 쓰므로, std::cout과 printf를 섞어도 순서가 지켜집니다. 첫 입출력 전에 std::ios::sync_with_stdio(false)를 부르면 C++ 스트림이 따로 버퍼를 써서 경우에 따라 훨씬 빨라질 수 있지만, 그 뒤로는 printf와 섞은 출력의 순서가 바뀔 수 있습니다. 입출력을 이미 한 뒤에 부르면 결과를 구현이 정하므로, cppreference의 sync_with_stdio 설명대로 프로그램의 맨 앞에서 부릅니다.

파일 입출력의 단계

파일을 다루는 과정은 데이터의 방향을 정하고, 그에 맞는 스트림을 열고, 연결을 확인하고, 읽거나 쓰고, 그 결과를 확인한 뒤 닫는 순서입니다. 아래 그림은 방향을 고르는 데서 시작해 열기에 성공했는지에 따라 갈라지는 흐름을 보여 줍니다.

C++ 파일 스트림 선택과 상태 확인 흐름

선택한 하나의 방향/모드로 open→is_open→작업 결과를 확인하는 실제 연결 상태 절차이다. ifstream/ofstream/fstream 세 대안을 연속 실행하지 않는다.

읽기·쓰기 방향에 맞는 파일 스트림과 openmode를 고르고 연결 상태와 작업 결과를 구분해 확인하는 C++ 파일 입출력 흐름

C++ · file I/O · decision flow

방향을 먼저 고르고, 연결과 작업 상태를 따로 확인한다

클래스의 기본 방향과 명시한 openmode는 같은 규칙이 아닙니다. 특히 std::fstream에 모드를 넘기면 그 조합을 그대로 사용하므로 app만으로 in | out이 생기지 않습니다.

방향선택에서파일연결성공또는열기오류로분기한다방향선택→입력/출력/양방향세대안중하나→모드명시open→isopen.예는작업결과확인,아니요는열기오류다.세스트림종류를모두차례대로실행하지않는fan-out/fan-in이다.예경로가열기오류노드를거치지않는다.모바일에서도현재불리언상태와파일연관을구별한다.데이터 방향은?읽기 / 쓰기 / 양방향입력ifstream · mode|in출력ofstream · mode|out양방향fstream · mode그대로의도한 모드로 openout|app 등 명시is_open()?파일 연결 확인예작업 결과 확인읽기 성공 반복쓰기·닫기 상태아니요열기 오류경로·권한·상태
파일 스트림 선택부터 작업 결과 확인까지의 흐름 읽기, 쓰기, 양방향 중 하나를 선택해 ifstream, ofstream, fstream으로 나뉜다. 명시적 모드로 파일을 열고 is_open으로 연결을 확인한 뒤 성공한 경우 작업 결과를 조건으로 반복한다. 데이터 방향은? read · write · both read write both std::ifstream mode | in std::ofstream mode | out std::fstream mode 그대로 모드를 명시하고 open out | app · in | out | app is_open()? 파일 연결 여부 no 오류 처리 failbit · path · 권한 yes 읽기·쓰기 결과 확인 읽기: 성공 반복 · 쓰기: 상태 확인
  1. 방향 선택

    읽기는 ifstream, 쓰기는 ofstream, 양방향은 fstream을 고릅니다.

  2. 모드 명시

    fstream 추가 쓰기는 out | app, 양방향 추가는 in | out | app처럼 의도를 모두 씁니다.

  3. 연결 확인

    open() 뒤 is_open()으로 파일 연관 여부를 봅니다.

  4. 읽기·쓰기 결과 확인

    읽기는 추출·getline 성공으로 반복하고, 쓰기는 작업 후와 필요한 닫기 후 상태를 확인합니다.

  5. RAII 정리

    보통 소멸자가 닫습니다. 조기 해제나 닫기 오류 확인이 필요할 때만 close()를 명시합니다.

formal mode

클래스마다 모드 합성 규칙이 다르다

ifstream은 in, ofstream은 out을 합칩니다. fstream의 in | out은 모드를 생략했을 때의 기본값이며, 넘긴 모드에는 자동으로 붙지 않습니다.

state

연결, 정상 상태, EOF를 구분한다

is_open()은 파일 연관만, if (file)은 현재 failbit·badbit 부재를 봅니다. eof()는 eofbit 보고값이므로 읽기 루프 조건을 대신하지 않습니다.

formatted → line

>> 뒤 남은 줄바꿈을 처리한다

in >> number;
std::getline(in >> std::ws, line);

std::ws는 줄바꿈을 포함한 선행 공백을 제거하므로 그 동작이 의도에 맞을 때 사용합니다.

lifetime

정상 경로는 소멸자에 맡긴다

파일 스트림의 수명을 스코프로 제한하면 소멸 시 버퍼가 닫힙니다. 명시적 close()는 더 이른 경계나 오류 확인이라는 이유가 있을 때 사용합니다.

std::ofstream(path)의 기본 인수는 out입니다. 그 기본 호출의 out 단독 조합은 파일 버퍼에서 기존 내용을 비우는 쓰기 모드로 매핑되므로 이어 쓰기는 app을 명시합니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목방향 선택에서 파일 연결 성공 또는 열기 오류로 분기한다
관계·조건 설명방향 선택→입력/출력/양방향 세 대안 중 하나→모드 명시 open→is_open으로 이어진다. 예 분기는 작업 결과 확인, 아니요 분기는 열기 오류다. 세 스트림 종류를 모두 차례대로 실행하지 않는 fan-out/fan-in이다. 예 경로가 열기 오류 노드를 거치지 않는다. 모바일에서도 현재 불리언 상태와 파일 연관을 구별한다.
노드·연결선 라벨fstream · mode 그대로
본문·주석파일 스트림의 수명을 스코프로 제한하면 소멸 시 연결된 파일이 닫힙니다. 명시적
예의 값과 변화
관찰·상황값·보충 정보변화·이유
모드 선택

읽기/쓰기 중 이 사례의 방향·모드를 정한다.

  • 설정: 선택한 한 stream/모드
  • 상태: 아직 열리지 않음
  • ifstream/ofstream/fstream은 한 실행의 연속 단계가 아니다.
open 검사

연결 성공 여부를 is_open으로 확인한다.

  • 성공: 작업 가능
  • 실패: 작업 경로 진입하지 않음
  • 연결 실패를 성공한 I/O처럼 처리하지 않는다.
작업 결과

연결 성공 뒤 작업의 상태도 확인한다.

  • 연결: 성공 상태
  • 확인: 읽기/쓰기 결과
  • open 성공만으로 뒤의 모든 I/O 성공을 보장하지 않는다.

C++ 파일 스트림 선택과 상태 확인 흐름

선택한 하나의 방향/모드로 open→is_open→작업 결과를 확인하는 실제 연결 상태 절차이다. ifstream/ofstream/fstream 세 대안을 연속 실행하지 않는다.

읽기·쓰기 방향에 맞는 파일 스트림과 openmode를 고르고 연결 상태와 작업 결과를 구분해 확인하는 C++ 파일 입출력 흐름

C++ · file I/O · decision flow

방향을 먼저 고르고, 연결과 작업 상태를 따로 확인한다

클래스의 기본 방향과 명시한 openmode는 같은 규칙이 아닙니다. 특히 std::fstream에 모드를 넘기면 그 조합을 그대로 사용하므로 app만으로 in | out이 생기지 않습니다.

방향선택에서파일연결성공또는열기오류로분기한다방향선택→입력/출력/양방향세대안중하나→모드명시open→isopen.예는작업결과확인,아니요는열기오류다.세스트림종류를모두차례대로실행하지않는fan-out/fan-in이다.예경로가열기오류노드를거치지않는다.모바일에서도현재불리언상태와파일연관을구별한다.데이터 방향은?읽기 / 쓰기 / 양방향입력ifstream · mode|in출력ofstream · mode|out양방향fstream · mode그대로의도한 모드로 openout|app 등 명시is_open()?파일 연결 확인예작업 결과 확인읽기 성공 반복쓰기·닫기 상태아니요열기 오류경로·권한·상태
파일 스트림 선택부터 작업 결과 확인까지의 흐름 읽기, 쓰기, 양방향 중 하나를 선택해 ifstream, ofstream, fstream으로 나뉜다. 명시적 모드로 파일을 열고 is_open으로 연결을 확인한 뒤 성공한 경우 작업 결과를 조건으로 반복한다. 데이터 방향은? read · write · both read write both std::ifstream mode | in std::ofstream mode | out std::fstream mode 그대로 모드를 명시하고 open out | app · in | out | app is_open()? 파일 연결 여부 no 오류 처리 failbit · path · 권한 yes 읽기·쓰기 결과 확인 읽기: 성공 반복 · 쓰기: 상태 확인
  1. 방향 선택

    읽기는 ifstream, 쓰기는 ofstream, 양방향은 fstream을 고릅니다.

  2. 모드 명시

    fstream 추가 쓰기는 out | app, 양방향 추가는 in | out | app처럼 의도를 모두 씁니다.

  3. 연결 확인

    open() 뒤 is_open()으로 파일 연관 여부를 봅니다.

  4. 읽기·쓰기 결과 확인

    읽기는 추출·getline 성공으로 반복하고, 쓰기는 작업 후와 필요한 닫기 후 상태를 확인합니다.

  5. RAII 정리

    보통 소멸자가 닫습니다. 조기 해제나 닫기 오류 확인이 필요할 때만 close()를 명시합니다.

formal mode

클래스마다 모드 합성 규칙이 다르다

ifstream은 in, ofstream은 out을 합칩니다. fstream의 in | out은 모드를 생략했을 때의 기본값이며, 넘긴 모드에는 자동으로 붙지 않습니다.

state

연결, 정상 상태, EOF를 구분한다

is_open()은 파일 연관만, if (file)은 현재 failbit·badbit 부재를 봅니다. eof()는 eofbit 보고값이므로 읽기 루프 조건을 대신하지 않습니다.

formatted → line

>> 뒤 남은 줄바꿈을 처리한다

in >> number;
std::getline(in >> std::ws, line);

std::ws는 줄바꿈을 포함한 선행 공백을 제거하므로 그 동작이 의도에 맞을 때 사용합니다.

lifetime

정상 경로는 소멸자에 맡긴다

파일 스트림의 수명을 스코프로 제한하면 소멸 시 버퍼가 닫힙니다. 명시적 close()는 더 이른 경계나 오류 확인이라는 이유가 있을 때 사용합니다.

std::ofstream(path)의 기본 인수는 out입니다. 그 기본 호출의 out 단독 조합은 파일 버퍼에서 기존 내용을 비우는 쓰기 모드로 매핑되므로 이어 쓰기는 app을 명시합니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목방향 선택에서 파일 연결 성공 또는 열기 오류로 분기한다
관계·조건 설명방향 선택→입력/출력/양방향 세 대안 중 하나→모드 명시 open→is_open으로 이어진다. 예 분기는 작업 결과 확인, 아니요 분기는 열기 오류다. 세 스트림 종류를 모두 차례대로 실행하지 않는 fan-out/fan-in이다. 예 경로가 열기 오류 노드를 거치지 않는다. 모바일에서도 현재 불리언 상태와 파일 연관을 구별한다.
노드·연결선 라벨fstream · mode 그대로
본문·주석파일 스트림의 수명을 스코프로 제한하면 소멸 시 연결된 파일이 닫힙니다. 명시적
예의 값과 변화
관찰·상황값·보충 정보변화·이유
모드 선택

읽기/쓰기 중 이 사례의 방향·모드를 정한다.

  • 설정: 선택한 한 stream/모드
  • 상태: 아직 열리지 않음
  • ifstream/ofstream/fstream은 한 실행의 연속 단계가 아니다.
open 검사

연결 성공 여부를 is_open으로 확인한다.

  • 성공: 작업 가능
  • 실패: 작업 경로 진입하지 않음
  • 연결 실패를 성공한 I/O처럼 처리하지 않는다.
작업 결과

연결 성공 뒤 작업의 상태도 확인한다.

  • 연결: 성공 상태
  • 확인: 읽기/쓰기 결과
  • open 성공만으로 뒤의 모든 I/O 성공을 보장하지 않는다.

스트림 객체를 만들면서 파일 이름을 넘기면 바로 열리고, 객체를 먼저 만든 뒤 open으로 열 수도 있습니다. 둘째 인자로 열기 모드(open mode)를 넘길 수 있으며, 여러 모드는 |로 합칩니다.

open_modes.cpp
#include <fstream>

int main() {
    std::ofstream out("example.txt");                  // 쓰기, 기존 내용 지움
    std::ifstream in("data.txt");                      // 읽기
    std::ofstream log("log.txt", std::ios::app);       // 끝에 이어 쓰기
    std::fstream both("save.dat", std::ios::in | std::ios::out);
}
모드뜻
std::ios::in읽기를 허용합니다.
std::ios::out쓰기를 허용합니다.
std::ios::app쓸 때마다 파일 끝으로 가서 이어 씁니다.
std::ios::ate연 직후 위치를 파일 끝으로 옮기며, 그 뒤 위치는 옮길 수 있습니다.
std::ios::trunc열 때 기존 내용을 지웁니다.
std::ios::binary줄바꿈 변환 같은 텍스트 처리 없이 엽니다.

std::ifstream은 넘긴 모드에 in을, std::ofstream은 out을 항상 합쳐서 엽니다. std::ofstream을 모드 없이 열면 기존 파일의 내용이 지워지므로, 이어 쓰려면 std::ios::app을 넘깁니다. std::fstream은 모드를 생략했을 때만 in | out이 기본이고 모드를 넘기면 그 값을 그대로 쓰므로, std::ios::app만 넘기면 읽기는 허용되지 않습니다. 바이너리 모드는 바이너리 파일 입출력에서 다룹니다.

연결 상태와 작업 결과

파일 열기는 파일이 없거나, 권한이 없거나, 경로가 틀리면 실패할 수 있습니다. is_open()은 스트림이 파일과 연결되었는지를 알려 주므로, 열자마자 확인합니다.

연결된 뒤의 읽기와 쓰기도 실패할 수 있으며, 그 결과는 스트림의 상태 플래그(state flag)에 남습니다. if (stream)은 마지막 작업이 실패해 failbit나 badbit가 설정되지 않았는지를 묻는 것이어서, 연결 여부를 묻는 is_open()과는 다른 질문입니다. eof()는 읽다가 파일 끝에 닿았다는 표시일 뿐이므로, 읽기 루프는 while (std::getline(in, line))처럼 읽기 작업 자체의 성공을 조건으로 씁니다.

파일 스트림 객체가 블록을 벗어나 사라지면 파일도 자동으로 닫힙니다. 동적 메모리 할당에서 본 RAII와 같은 방식이어서, 보통은 직접 닫지 않아도 됩니다. 쓰기가 끝났는지 확인해야 하거나 파일을 일찍 놓아야 할 때만 close()를 부르고, 그 뒤 스트림의 상태를 확인합니다.

파일 쓰기 예

다음 프로그램은 세 줄을 파일에 쓰고, 쓰기와 닫기가 모두 성공했는지 확인합니다. std::cout과 같은 <<로 쓰지만 대상이 화면이 아니라 outFile이며, 상태는 열기 직후와 close() 직후에 한 번씩 확인합니다. 두 확인을 모두 통과해야 화면에 saved my_output.txt가 출력되고, 파일에 들어간 세 줄은 다음 절의 읽기 예제로 확인합니다.

write_text.cpp
#include <fstream>
#include <iostream>

int main() {
    std::ofstream outFile("my_output.txt");
    if (!outFile.is_open()) {
        std::cerr << "cannot open my_output.txt\n";
        return 1;
    }

    outFile << "first line in the file\n";
    outFile << "number: " << 12345 << '\n';
    outFile << "name: " << "Ada" << '\n';

    outFile.close();
    if (!outFile) {
        std::cerr << "failed to finish writing my_output.txt\n";
        return 1;
    }
    std::cout << "saved my_output.txt\n";
}
saved my_output.txt

<<로 쓴 내용은 곧바로 파일에 가지 않고 먼저 버퍼에 모였다가, 버퍼가 차거나 파일을 닫을 때 기록됩니다. 그래서 close()한 뒤에 상태를 확인해야 마지막 기록까지 성공했는지 알 수 있습니다. 쓰기 도중에 실패하면 파일은 이미 만들어졌거나 기존 내용이 지워졌을 수 있고, 새 내용은 일부만 남을 수 있습니다. 버퍼와 쓰기 오류는 텍스트 파일 쓰기에서 자세히 봅니다.

파일 읽기 예

다음 프로그램은 앞에서 만든 파일을 한 줄씩 읽어 화면에 출력합니다. 쓰기 예제와 같은 폴더에서 실행해야 my_output.txt를 찾을 수 있으며, 파일을 열지 못하면 메시지를 출력하고 1을 돌려줍니다. 출력의 세 줄이 쓰기 예제가 파일에 넣은 세 줄과 같은지 비교해 보세요.

read_text.cpp
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream inFile("my_output.txt");
    if (!inFile.is_open()) {
        std::cerr << "cannot open my_output.txt\n";
        return 1;
    }

    std::string line;
    while (std::getline(inFile, line)) {
        std::cout << line << '\n';
    }

    if (inFile.bad()) {
        std::cerr << "read error\n";
        return 1;
    }
}
first line in the file
number: 12345
name: Ada

std::getline은 한 줄을 읽는 데 성공하면 참으로 해석되므로, 세 줄을 읽은 뒤 더 읽을 줄이 없을 때 루프가 끝납니다. 루프가 끝난 이유가 정상적인 파일 끝인지 읽기 오류인지는 bad()로 구분합니다. 두 프로그램은 각자 main을 가지므로 따로 빌드합니다.

g++ -std=c++20 -Wall -Wextra write_text.cpp -o write_text.exe
g++ -std=c++20 -Wall -Wextra read_text.cpp -o read_text.exe
.\write_text.exe
.\read_text.exe

상대 경로와 작업 폴더

"my_output.txt"처럼 폴더 없이 적은 상대 경로는 소스 파일이나 실행 파일의 위치가 아니라, 프로그램을 실행한 현재 작업 폴더(current working directory)를 기준으로 찾습니다. 위 명령처럼 같은 폴더에서 두 프로그램을 실행하면 같은 파일을 쓰고 읽지만, 다른 폴더에서 실행하면 읽기 프로그램은 파일을 찾지 못합니다.

에디터의 실행 버튼은 작업 폴더를 프로젝트 폴더나 빌드 폴더로 정하는 경우가 많으므로, 파일을 찾지 못한다면 먼저 작업 폴더가 어디인지 확인합니다. 원하는 위치가 정해져 있다면 그 위치를 절대 경로로 넘기거나, 실행할 때 경로를 인자로 받는 방법이 확실합니다.

정리

  • 스트림은 형식화를 맡고, 연결된 버퍼가 키보드, 화면, 파일과 실제로 바이트를 주고받습니다.
  • 파일 스트림은 읽기용 std::ifstream, 쓰기용 std::ofstream, 둘 다 하는 std::fstream이며 <fstream>에 있습니다.
  • std::ofstream을 모드 없이 열면 기존 내용이 지워지므로, 이어 쓰려면 std::ios::app을 넘깁니다.
  • is_open()은 연결 여부를, if (stream)은 마지막 작업의 성공 여부를 알려 주며, 읽기 루프는 읽기 작업을 조건으로 씁니다.
  • 파일 스트림은 블록을 벗어나면 자동으로 닫히고, 쓰기 결과를 확인하려면 close() 뒤 상태를 봅니다.
  • 상대 경로는 실행할 때의 작업 폴더를 기준으로 찾습니다.

연습 문제

1. 다른 폴더에서 읽기

read_text.exe만 빈 폴더에 복사하고 그 폴더에서 실행하면 무엇이 출력되고, 종료 상태는 무엇인가요?

해설 보기

그 폴더에는 my_output.txt가 없으므로 열기에 실패해 cannot open my_output.txt를 표준 오류로 출력하고, 종료 상태 1로 끝납니다. 파일의 세 줄은 출력되지 않습니다. 상대 경로는 실행 파일의 위치가 아니라 작업 폴더를 기준으로 찾기 때문입니다.

2. 두 번 실행한 쓰기 프로그램

write_text.exe를 두 번 실행한 뒤 read_text.exe를 실행하면 몇 줄이 출력되나요? std::ofstream outFile("my_output.txt", std::ios::app);으로 바꾸고 같은 일을 하면 어떻게 되나요?

해설 보기

모드 없이 연 std::ofstream은 열 때마다 기존 내용을 지우므로, 두 번 실행해도 파일에는 마지막에 쓴 세 줄만 남고 세 줄이 출력됩니다. std::ios::app으로 열면 매번 끝에 이어 쓰므로, 파일이 처음에 없었다면 두 번 실행한 뒤 같은 세 줄이 두 번 반복된 여섯 줄이 출력됩니다.

3. fstream에 넘긴 모드

std::fstream file("log.txt", std::ios::app);으로 연 스트림으로 std::getline(file, line)을 하면 읽을 수 있나요?

해설 보기

읽을 수 없습니다. std::fstream은 모드를 넘기면 그 값을 그대로 쓰므로 in이 들어가지 않고, 읽기가 허용되지 않아 읽기 작업이 실패합니다. 이어 쓰면서 읽기도 하려면 std::ios::in | std::ios::out | std::ios::app처럼 필요한 모드를 모두 적습니다.

다음 글 텍스트 파일 읽기에서는 std::ifstream으로 공백으로 구분된 값과 한 줄 단위의 데이터를 읽고, 상태 플래그로 읽기 실패의 원인을 구분하는 방법을 다룹니다.