텍스트 파일 쓰기
std::ofstream으로 문자열과 숫자를 기록하고 덮어쓰기·추가 모드, 버퍼 반영 시점과 쓰기 오류를 관리합니다.
반대로, 프로그램에서 생성된 데이터를 텍스트 파일에 저장하는 방법을 다룹니다.
텍스트 파일에 데이터를 쓰는 것은 사용자 설정 저장, 로그 기록, 결과 출력 등 많은 애플리케이션에서 필수적인 기능입니다.
이 장에서는 std::ofstream을 사용하여 텍스트 파일에 문자열과 숫자를 쓰는 방법, 파일 열기 모드, 출력 버퍼를 다룹니다.
std::ios::out과 std::ios::app의 차이, 버퍼에서 파일로 반영되는 시점, fail()/bad() 점검 위치를 한 번에 정리한 다이어그램입니다.
문자열을 `<<`로 보내는 것만으로는 충분하지 않습니다. 기존 파일을 지울지, 뒤에 붙일지, 버퍼가 언제 파일에 반영되는지, 쓰기 실패를 어디서 볼지 함께 정해야 합니다.
- 모드 결정
out / app 새로 쓸지, 끝에 붙일지 먼저 고릅니다.
- 열기 확인
is_open() 경로와 권한 문제를 쓰기 전에 잡습니다.
- 버퍼에 출력
out << line 문자열과 줄바꿈을 스트림 버퍼에 보냅니다.
- 파일 반영
flush / close 버퍼 내용을 디스크로 밀어냅니다.
- 상태 점검
fail / bad 쓰기 실패와 장치 오류를 확인합니다.
std::ofstream로 텍스트 파일 열기
텍스트 파일에 데이터를 쓰기 위해서는 std::ofstream 클래스의 객체를 사용합니다.
ofstream은 Output File Stream의 약자입니다.
ofstream 객체를 생성할 때 파일 이름을 인자로 전달하여 바로 열거나, 객체 생성 후 open() 멤버 함수를 호출하여 열 수 있습니다.
#include <fstream> // std::ofstream을 위해
#include <iostream> // std::cout, std::endl을 위해
int main() {
// 방법 1: 생성과 동시에 파일 열기
std::ofstream outFile1("output1.txt");
if (!outFile1.is_open()) { // 또는 if (outFile1.fail())
std::cerr << "output1.txt를 열 수 없습니다!\n";
} else {
// 파일 처리...
outFile1.close();
}
// 방법 2: 객체 생성 후 open() 호출
std::ofstream outFile2;
outFile2.open("output2.txt");
if (!outFile2) { // 스트림 객체 자체를 bool 컨텍스트에서 사용하여 성공 여부 확인 가능
std::cerr << "output2.txt를 열 수 없습니다!\n";
} else {
// 파일 처리...
outFile2.close();
}
return 0;
}std::ofstream은 기본적으로 std::ios::out 모드로 파일을 엽니다.
이 모드는 다음과 같은 특징을 가집니다.
- 지정된 이름의 파일이 존재하지 않으면 새로 생성합니다.
- 지정된 이름의 파일이 이미 존재하면, 그 파일의 모든 내용을 지우고(truncate) 새로운 내용을 씁니다. (주의!)
파일 쓰기 (<< 연산자)
콘솔에 std::cout을 사용하여 데이터를 출력하듯이, 파일 스트림에서도 << (삽입) 연산자를 사용하여 데이터를 파일에 쓸 수 있습니다.
다양한 데이터 타입(문자열, 정수, 실수 등)을 파일에 쓸 수 있으며, std::endl을 사용하여 개행 문자를 삽입하고 출력 버퍼를 비울 수 있습니다.
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ofstream outFile("my_data.txt"); // my_data.txt 파일을 쓰기 모드로 엶
if (!outFile.is_open()) {
std::cerr << "오류: my_data.txt 파일을 열 수 없습니다!\n";
return 1;
}
// 문자열 쓰기
outFile << "Hello, File World!\n"; // '\n'은 개행 문자
outFile << "C++ 파일 입출력 예제입니다." << std::endl; // std::endl은 '\n' + flush
// 숫자 쓰기
int age = 30;
double temperature = 25.7;
outFile << "나이: " << age << "세" << std::endl;
outFile << "오늘의 온도: " << temperature << "도" << std::endl;
// 여러 값 한 줄에 쓰기
std::string product = "Laptop";
int quantity = 5;
double price = 1200.50;
outFile << product << ", " << quantity << ", " << price << std::endl;
std::cout << "데이터가 'my_data.txt' 파일에 성공적으로 기록되었습니다." << std::endl;
outFile.close(); // 파일 닫기
return 0;
}위 코드를 실행하면, my_data.txt 파일이 생성되거나 기존 내용이 지워지고 다음과 같은 내용이 기록됩니다.
Hello, File World!
C++ 파일 입출력 예제입니다.
나이: 30세
오늘의 온도: 25.7도
Laptop, 5, 1200.5파일 열기 모드 (Open Modes)의 중요성
텍스트 파일 쓰기는 열기 모드, 상태 플래그, 읽기·쓰기 단위, 닫기 책임으로 확인합니다.
텍스트 파일 쓰기의 가장 큰 위험은 코드가 아니라 모드 선택에서 시작됩니다. 같은 `<<` 출력이라도 `out`, `trunc`, `app` 조합에 따라 결과 파일이 달라집니다.
- 의도결과 파일 새로 만들기
의도 결과 파일 새로 만들기
- 코드코드 ofstream out(path)
- 기존 파일 결과기존 파일 결
기존 내용이 새 출력으로 교체됩니다.
- 주의주의 실수
로그를 지울 수 있습니다.
- 의도로그 이어 쓰기
의도 로그 이어 쓰기
- 코드코드 ofstream out(path, ios
app)
- 기존 파일 결과기존 파일 결
기존 내용 뒤에 새 줄이 붙습니다.
- 주의주의 중복 기록 정책
따로 봅니다.
- 의도바이트 그대로 쓰기
의도 바이트 그대로 쓰기
- 코드코드 ios
binary
- 기존 파일 결과기존 파일 결
줄바꿈 변환 없이 바이트 단위로 기록합니다.
- 주의주의 텍스트 파일
읽기 방식이 달라집니다.
텍스트 파일 쓰기에서는 파일을 여는 모드, 버퍼 반영 시점, 쓰기 후 오류 확인 위치를 함께 잡아야 합니다.
`operator<<`는 값을 스트림 버퍼에 넣습니다. 파일에 남는 최종 결과는 열기 모드와 flush 또는 close 시점, 그리고 쓰기 상태 확인에 의해 결정됩니다.
- mode
덮어쓰기인지 이어쓰기인지 먼저 고릅니다.
- open
경로와 권한 때문에 열기 실패가 없는지 봅니다.
- format
줄바꿈, 구분자, precision을 명확히 둡니다.
- state
쓰기 후 fail/bad로 디스크 오류를 확인합니다.
std::ios::out 모드는 기본적으로 파일의 내용을 지우고 새롭게 쓰기 시작합니다.
만약 기존 파일의 내용을 유지하면서 새로운 데이터를 추가하고 싶다면, std::ios::app 모드를 사용해야 합니다.
std::ios::out(기본값): 파일을 쓰기 모드로 열고, 기존 내용이 있으면 모두 지웁니다.std::ios::app: 파일을 쓰기 모드로 열고, 파일의 맨 끝에 새로운 내용을 추가(append)합니다. 기존 내용은 보존됩니다.
#include <iostream>
#include <fstream>
#include <string>
#include <chrono> // 현재 시간을 위해
#include <iomanip> // std::put_time을 위해
int main() {
std::ofstream logFile;
// std::ios::app 모드로 파일 열기. 파일이 없으면 생성, 있으면 끝에 추가
logFile.open("application_log.txt", std::ios::app);
if (!logFile.is_open()) {
std::cerr << "오류: application_log.txt 파일을 열 수 없습니다!\n";
return 1;
}
// 현재 시간을 가져와서 로그에 포함
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
// 시간 형식을 지정하여 출력
// std::put_time은 C++11부터 사용 가능하며, <iomanip> 헤더 필요
logFile << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S") << ": ";
logFile << "애플리케이션이 실행되었습니다." << std::endl;
logFile << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S") << ": ";
logFile << "사용자 'Admin'이 로그인했습니다." << std::endl;
std::cout << "로그가 'application_log.txt'에 추가되었습니다." << std::endl;
logFile.close();
return 0;
}위 코드를 여러 번 실행해 보면, application_log.txt 파일의 내용이 계속해서 늘어나는 것을 확인할 수 있습니다.
출력 버퍼와 flush()
파일 스트림은 효율적인 데이터 전송을 위해 버퍼링(Buffering)을 사용합니다.
데이터를 파일에 바로 쓰지 않고, 일단 메모리의 임시 저장 공간(버퍼)에 모아둡니다.
버퍼가 가득 차거나, std::endl을 사용하거나, flush() 멤버 함수를 호출하거나, 파일을 닫을 때(스트림 객체 소멸 시) 버퍼의 내용이 실제 파일에 기록됩니다.
std::endl: 개행 문자를 삽입하고 버퍼를flush()합니다.\n: 단순히 개행 문자만 삽입하고 버퍼를flush()하지 않습니다.std::flush: 버퍼의 내용을 즉시 파일에 쓰도록 강제합니다.
#include <iostream>
#include <fstream>
#include <string>
#include <thread> // std::this_thread::sleep_for
#include <chrono> // std::chrono::seconds
int main() {
std::ofstream outFile("buffered_output.txt");
if (!outFile.is_open()) {
std::cerr << "오류: buffered_output.txt 파일을 열 수 없습니다!\n";
return 1;
}
outFile << "이것은 첫 번째 줄입니다.\n"; // '\n'만 사용, flush 안됨
std::cout << "버퍼에만 쓰여졌을 수 있습니다. 잠시 대기..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 2초 대기
// 버퍼의 내용을 강제로 파일에 기록
outFile.flush();
std::cout << "버퍼가 플러시되었습니다. 이제 파일에 기록되었을 것입니다." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 2초 대기
outFile << "이것은 두 번째 줄입니다." << std::endl; // std::endl 사용, flush 됨
std::cout << "std::endl로 인해 버퍼가 플러시되었습니다." << std::endl;
outFile.close(); // 파일 닫기 시 자동으로 flush
std::cout << "파일이 닫혔습니다." << std::endl;
return 0;
}일반적으로 std::endl을 사용하여 개행과 플러시를 함께 처리하는 것이 편리합니다.
그러나 실시간으로 데이터가 파일에 기록되어야 하는 매우 중요한 상황(예: 오류 로그, 중요한 데이터 저장)에서는 명시적으로 flush()를 호출하여 데이터 유실을 방지하는 것이 좋습니다.
파일 쓰기 오류 처리
파일 쓰기 중에도 여러 가지 오류가 발생할 수 있습니다.
예를 들어 디스크 공간 부족, 파일 시스템 오류, 접근 권한 없음 등입니다.
읽기에서와 마찬가지로 fail()이나 bad() 멤버 함수를 사용하여 오류를 확인해야 합니다.
#include <iostream>
#include <fstream>
int main() {
std::ofstream outFile("protected_file.txt"); // 쓰기 권한이 없는 파일 또는 디스크 가득 참 가정
if (!outFile.is_open()) {
std::cerr << "파일을 열 수 없습니다. 쓰기 권한을 확인하세요.\n";
return 1;
}
outFile << "데이터를 씁니다.";
if (outFile.fail()) {
std::cerr << "파일 쓰기 중 오류가 발생했습니다. (fail())\n";
outFile.clear(); // 오류 플래그 초기화
}
if (outFile.bad()) {
std::cerr << "파일 쓰기 중 심각한 오류가 발생했습니다. (bad())\n";
outFile.clear(); // 오류 플래그 초기화
}
outFile.close();
return 0;
}텍스트 파일 쓰기는 열기 성공만으로 끝나지 않으므로, 버퍼 반영과 상태 확인까지 같은 흐름에서 점검해야 합니다.
파일을 열고 데이터를 보낸 뒤에도 버퍼와 상태 플래그가 남아 있습니다. 중요한 저장 코드는 쓰기 후 확인까지 포함해야 안전합니다.
- 의도 선택
mode 새 파일인지 추가 기록인지 결정합니다.
- 열기 확인
if (out) 경로와 권한 문제를 본문 처리 전에 거릅니다.
- 버퍼 기록
<< 출력 연산은 먼저 스트림 버퍼에 쌓입니다.
- 반영 시점
flush / close 중요 로그는 명시적으로 밀어냅니다.
- 상태 확인
fail / bad 쓰기 실패와 장치 오류를 마지막에 점검합니다.
쓰기 결과가 예상과 다를 때는 증상을 기준으로 모드, 버퍼, 상태 플래그를 거꾸로 추적하면 원인을 빠르게 좁힐 수 있습니다.
텍스트 쓰기 문제는 대부분 열기 모드, 버퍼 반영 시점, 상태 플래그 중 하나에서 시작됩니다. 증상을 기준으로 보면 원인을 빠르게 좁힐 수 있습니다.
- 증상기존 내용이 사라짐
증상 기존 내용이 사라짐
- 먼저 볼 위치먼저 볼 위치 out / trunc 기본 쓰기 모드
파일을 비웠는지 봅니다.
- 수정 방향수정 방향 로그라면 ios
app 을 명시합니다.
- 증상파일에 바로 안 보임
증상 파일에 바로 안 보임
- 먼저 볼 위치먼저 볼 위치 flush / close 버퍼
아직 OS로 전달되지 않았을 수 있습니다.
- 수정 방향수정 방향 중요 기록
flush 시점을 명확히 둡니다.
- 증상쓰기 실패
증상 쓰기 실패
- 먼저 볼 위치먼저 볼 위치 fail /
bad 권한, 디스크 공간, 장치 오류를 확인합니다.
- 수정 방향수정 방향 쓰기 후 상태
확인하고 사용자에게 원인을 알립니다.
- 증상형식이 깨짐
증상 형식이 깨짐
- 먼저 볼 위치먼저 볼 위치 newline /
separator 줄바꿈과 구분자 정책을 봅니다.
- 수정 방향수정 방향 읽기 코드
같은 형식 계약을 유지합니다.
ofstream 쓰기는 모드, 버퍼, 상태 플래그를 같이 봐야 파일 손실을 막을 수 있습니다.
텍스트 파일 쓰기는 문자열을 내보내는 일처럼 보이지만, 실제 버그는 열기 모드, flush 시점, 실패 상태를 놓칠 때 생깁니다.
- open
path + mode 경로와 모드를 결정합니다.
- write
<< 문자열과 줄바꿈을 버퍼에 보냅니다.
- flush
endl / flush 필요한 시점에 파일로 반영합니다.
- state
fail / bad 쓰기 실패와 심각 오류를 확인합니다.
- 기존 내용 삭제 후 기록
truncate 기존 내용 삭제 후 기록 설정 파일처럼 전체 내용을 새로 쓰는 경우에 적합하지만 백업을 고려합니다.
- 파일 끝에 추가
append 파일 끝에 추가 로그처럼 기존 기록을 유지해야 할 때 사용합니다.
- 버퍼를 OS로 밀어냄
flush 버퍼를 OS로 밀어냄 `endl`은 줄바꿈과 flush를 함께 하므로 잦은 사용은 비용이 됩니다.
- open 성공만으로 충분하지 않음
error open 성공만으로 충분하지 않음 쓰기 중 디스크 공간, 권한, 장치 오류가 뒤늦게 드러날 수 있습니다.
std::ofstream out(path, std::ios::app);
if (!out) throw std::runtime_error("open failed");
out << line << '\n';
out.flush();
if (!out) throw std::runtime_error("write failed");