Topic 2: 이진 파일과 직렬화 - 효율적인 데이터 저장의 비밀 💾
🎯 학습 목표
- 텍스트 파일과 이진 파일의 차이점을 이해할 수 있다
- 이진 파일을 읽고 쓰는 방법을 익힐 수 있다
- 객체 직렬화의 개념과 필요성을 파악할 수 있다
- 구조체와 클래스를 파일에 저장하고 불러올 수 있다
- 게임 캐릭터 데이터를 효율적으로 관리할 수 있다
📸 사진 vs 글 - 이진 파일의 이해
텍스트 파일 = 글 📝
// 텍스트 파일에 저장된 숫자 1234
"1234" // 4개의 문자: '1', '2', '3', '4'
// 크기: 4바이트이진 파일 = 사진/음악 🖼️🎵
// 이진 파일에 저장된 숫자 1234
01001101 10010010 // 실제 이진 데이터
// 크기: 4바이트 (int형)사진을 메모장으로 열면 깨져 보이는 이유:
- 사진은 이진 데이터 (색상 정보)
- 메모장은 텍스트로 해석하려고 함
- → 깨져 보임! 🤯
🆚 텍스트 파일 vs 이진 파일 비교
| 구분 | 텍스트 파일 | 이진 파일 |
|---|---|---|
| 저장 방식 | 문자로 변환해서 저장 | 메모리 상태 그대로 저장 |
| 파일 크기 | 상대적으로 큼 | 상대적으로 작음 |
| 사람이 읽기 | 가능 📖 | 불가능 🚫 |
| 처리 속도 | 느림 (변환 필요) | 빠름 (직접 복사) |
| 용도 | 설정 파일, 로그 | 이미지, 게임 데이터 |
실제 크기 비교 예시
// 텍스트 파일에 저장
int num = 1234567890;
// "1234567890" → 10바이트
// 이진 파일에 저장
int num = 1234567890;
// 실제 int 값 → 4바이트 (60% 절약!)📂 이진 파일 쓰기 - write() 함수
기본 문법
file.write(reinterpret_cast<const char*>(&데이터), sizeof(데이터));간단한 숫자 저장 예제
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 이진 파일로 열기 (ios::binary 플래그!)
ofstream binFile("numbers.dat", ios::binary);
if (!binFile.is_open()) {
cout << "❌ 파일을 열 수 없습니다!" << endl;
return 1;
}
// 저장할 데이터들
int playerScore = 95000;
double playerHealth = 87.5;
bool isAlive = true;
cout << "💾 이진 데이터 저장 중..." << endl;
// 이진 데이터로 저장
binFile.write(reinterpret_cast<const char*>(&playerScore), sizeof(playerScore));
binFile.write(reinterpret_cast<const char*>(&playerHealth), sizeof(playerHealth));
binFile.write(reinterpret_cast<const char*>(&isAlive), sizeof(isAlive));
binFile.close();
cout << "✅ 이진 파일 저장 완료!" << endl;
cout << "파일 크기: " << sizeof(playerScore) + sizeof(playerHealth) + sizeof(isAlive) << " 바이트" << endl;
return 0;
}📖 이진 파일 읽기 - read() 함수
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 이진 파일에서 읽기
ifstream binFile("numbers.dat", ios::binary);
if (!binFile.is_open()) {
cout << "❌ 파일을 읽을 수 없습니다!" << endl;
return 1;
}
// 읽어올 변수들
int playerScore;
double playerHealth;
bool isAlive;
cout << "📂 이진 데이터 로딩 중..." << endl;
// 이진 데이터 읽기 (저장한 순서와 동일하게!)
binFile.read(reinterpret_cast<char*>(&playerScore), sizeof(playerScore));
binFile.read(reinterpret_cast<char*>(&playerHealth), sizeof(playerHealth));
binFile.read(reinterpret_cast<char*>(&isAlive), sizeof(isAlive));
binFile.close();
// 읽어온 데이터 출력
cout << "🎮 ===================" << endl;
cout << " 점수: " << playerScore << " 🏆" << endl;
cout << " 체력: " << playerHealth << " ❤️" << endl;
cout << " 생존: " << (isAlive ? "살아있음 😊" : "죽음 💀") << endl;
cout << "🎮 ===================" << endl;
cout << "✅ 이진 파일 로딩 완료!" << endl;
return 0;
}🎯 객체 직렬화 - 구조체 저장
게임 캐릭터 구조체 저장하기
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
// 게임 캐릭터 구조체
struct GameCharacter {
char name[50]; // string 대신 char 배열 사용 (크기 고정)
int level;
int hp;
int mp;
int gold;
double experience;
};
// 캐릭터를 이진 파일에 저장
void saveCharacter(const GameCharacter& character, const string& filename) {
ofstream file(filename, ios::binary);
if (!file.is_open()) {
cout << "❌ 캐릭터 저장 실패!" << endl;
return;
}
// 구조체 전체를 한 번에 저장!
file.write(reinterpret_cast<const char*>(&character), sizeof(character));
file.close();
cout << "✅ " << character.name << " 캐릭터 저장 완료! 💾" << endl;
}
// 캐릭터를 이진 파일에서 로드
bool loadCharacter(GameCharacter& character, const string& filename) {
ifstream file(filename, ios::binary);
if (!file.is_open()) {
cout << "❌ 캐릭터 로드 실패!" << endl;
return false;
}
// 구조체 전체를 한 번에 읽기!
file.read(reinterpret_cast<char*>(&character), sizeof(character));
file.close();
cout << "✅ " << character.name << " 캐릭터 로드 완료! 📂" << endl;
return true;
}
// 캐릭터 정보 출력
void printCharacter(const GameCharacter& character) {
cout << "⚔️ ========================" << endl;
cout << " 캐릭터명: " << character.name << endl;
cout << " 레벨: " << character.level << " 📊" << endl;
cout << " HP: " << character.hp << " ❤️" << endl;
cout << " MP: " << character.mp << " 💙" << endl;
cout << " 골드: " << character.gold << " 💰" << endl;
cout << " 경험치: " << character.experience << " ⭐" << endl;
cout << "⚔️ ========================" << endl;
}
int main() {
GameCharacter hero;
int choice;
cout << "🎮 캐릭터 관리 시스템 🎮" << endl;
while (true) {
cout << "\n1. 새 캐릭터 생성 👤" << endl;
cout << "2. 캐릭터 저장 💾" << endl;
cout << "3. 캐릭터 로드 📂" << endl;
cout << "4. 캐릭터 정보 보기 👁️" << endl;
cout << "5. 종료 🚪" << endl;
cout << "선택: ";
cin >> choice;
switch (choice) {
case 1: {
cout << "\n🆕 새 캐릭터를 생성합니다!" << endl;
cout << "캐릭터명: ";
cin >> hero.name;
hero.level = 1;
hero.hp = 100;
hero.mp = 50;
hero.gold = 100;
hero.experience = 0.0;
cout << "✅ " << hero.name << " 캐릭터가 생성되었습니다!" << endl;
break;
}
case 2: {
string filename = string(hero.name) + ".dat";
saveCharacter(hero, filename);
break;
}
case 3: {
cout << "로드할 캐릭터명: ";
string charName;
cin >> charName;
string filename = charName + ".dat";
if (loadCharacter(hero, filename)) {
printCharacter(hero);
}
break;
}
case 4: {
if (strlen(hero.name) > 0) {
printCharacter(hero);
} else {
cout << "❌ 캐릭터가 없습니다!" << endl;
}
break;
}
case 5:
cout << "👋 프로그램을 종료합니다!" << endl;
return 0;
default:
cout << "❌ 잘못된 선택입니다!" << endl;
}
}
return 0;
}🏆 고급 예제: 다중 캐릭터 저장 시스템
여러 캐릭터를 하나의 파일에 저장
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
using namespace std;
struct RPGCharacter {
char name[30];
char jobClass[20]; // 직업
int level;
int hp;
int mp;
int strength;
int intelligence;
int gold;
};
class CharacterManager {
private:
vector<RPGCharacter> characters;
string saveFile = "party.dat";
public:
// 새 캐릭터 추가
void createCharacter() {
if (characters.size() >= 4) {
cout << "❌ 파티는 최대 4명까지만 가능합니다!" << endl;
return;
}
RPGCharacter newChar = {};
cout << "🆕 새 캐릭터 생성" << endl;
cout << "이름: ";
cin >> newChar.name;
cout << "직업 (전사/마법사/도적/성직자): ";
cin >> newChar.jobClass;
// 직업별 기본 스탯 설정
newChar.level = 1;
newChar.gold = 100;
if (strcmp(newChar.jobClass, "전사") == 0) {
newChar.hp = 120;
newChar.mp = 30;
newChar.strength = 15;
newChar.intelligence = 8;
} else if (strcmp(newChar.jobClass, "마법사") == 0) {
newChar.hp = 80;
newChar.mp = 100;
newChar.strength = 8;
newChar.intelligence = 15;
} else if (strcmp(newChar.jobClass, "도적") == 0) {
newChar.hp = 90;
newChar.mp = 50;
newChar.strength = 12;
newChar.intelligence = 10;
} else if (strcmp(newChar.jobClass, "성직자") == 0) {
newChar.hp = 100;
newChar.mp = 80;
newChar.strength = 10;
newChar.intelligence = 12;
}
characters.push_back(newChar);
cout << "✅ " << newChar.name << " (" << newChar.jobClass << ") 생성 완료! ⚔️" << endl;
}
// 모든 캐릭터를 파일에 저장
void saveParty() {
if (characters.empty()) {
cout << "❌ 저장할 캐릭터가 없습니다!" << endl;
return;
}
ofstream file(saveFile, ios::binary);
if (!file.is_open()) {
cout << "❌ 파일 저장 실패!" << endl;
return;
}
// 캐릭터 수 저장
size_t count = characters.size();
file.write(reinterpret_cast<const char*>(&count), sizeof(count));
// 각 캐릭터 저장
for (const auto& character : characters) {
file.write(reinterpret_cast<const char*>(&character), sizeof(character));
}
file.close();
cout << "✅ 파티 데이터 저장 완료! 💾" << endl;
cout << "총 " << count << "명의 캐릭터가 저장되었습니다." << endl;
}
// 파일에서 캐릭터들 로드
void loadParty() {
ifstream file(saveFile, ios::binary);
if (!file.is_open()) {
cout << "❌ 저장된 파티가 없습니다!" << endl;
return;
}
// 캐릭터 수 읽기
size_t count;
file.read(reinterpret_cast<char*>(&count), sizeof(count));
characters.clear();
// 각 캐릭터 로드
for (size_t i = 0; i < count; i++) {
RPGCharacter character;
file.read(reinterpret_cast<char*>(&character), sizeof(character));
characters.push_back(character);
}
file.close();
cout << "✅ 파티 데이터 로드 완료! 📂" << endl;
cout << "총 " << count << "명의 캐릭터가 로드되었습니다." << endl;
}
// 파티 정보 출력
void showParty() {
if (characters.empty()) {
cout << "❌ 파티에 캐릭터가 없습니다!" << endl;
return;
}
cout << "\n👥 ==== 현재 파티 정보 ====" << endl;
for (size_t i = 0; i < characters.size(); i++) {
const auto& character = characters[i];
cout << "[" << (i + 1) << "] " << character.name
<< " (" << character.jobClass << ")" << endl;
cout << " 레벨: " << character.level << " | "
<< "HP: " << character.hp << " | "
<< "MP: " << character.mp << endl;
cout << " 힘: " << character.strength << " | "
<< "지능: " << character.intelligence << " | "
<< "골드: " << character.gold << "💰" << endl;
cout << endl;
}
cout << "👥 ========================" << endl;
}
};
int main() {
CharacterManager manager;
int choice;
cout << "🎮 RPG 파티 관리 시스템 🎮" << endl;
while (true) {
cout << "\n1. 캐릭터 생성 👤" << endl;
cout << "2. 파티 저장 💾" << endl;
cout << "3. 파티 로드 📂" << endl;
cout << "4. 파티 보기 👁️" << endl;
cout << "5. 종료 🚪" << endl;
cout << "선택: ";
cin >> choice;
switch (choice) {
case 1:
manager.createCharacter();
break;
case 2:
manager.saveParty();
break;
case 3:
manager.loadParty();
break;
case 4:
manager.showParty();
break;
case 5:
cout << "👋 게임을 종료합니다!" << endl;
return 0;
default:
cout << "❌ 잘못된 선택입니다!" << endl;
}
}
return 0;
}⚠️ 이진 파일 사용 시 주의사항
1. 플랫폼별 차이점
// Windows와 Linux에서 int 크기가 다를 수 있음
// 해결책: 고정 크기 타입 사용
#include <cstdint>
int32_t fixedSizeInt; // 항상 4바이트2. string 클래스 문제
// ❌ 잘못된 방법
struct BadExample {
string name; // 포인터만 저장됨!
};
// ✅ 올바른 방법
struct GoodExample {
char name[50]; // 실제 데이터 저장
};3. 읽기/쓰기 순서 일치
// 저장 순서와 로드 순서가 반드시 같아야 함!
// 저장: name → level → hp
// 로드: name → level → hp (동일한 순서!)💡 핵심 정리
- 이진 파일: 데이터를 메모리 상태 그대로 저장하는 방식
- 장점: 빠른 속도, 작은 파일 크기, 정확한 데이터 보존
- 단점: 사람이 읽을 수 없음, 플랫폼 의존성
- write(): 이진 데이터를 파일에 쓰기
- read(): 이진 데이터를 파일에서 읽기
- 직렬화: 객체나 구조체를 파일에 저장 가능한 형태로 변환
- 플래그:
ios::binary로 이진 모드 지정 필수
✅ 실습 체크리스트
🚀 다음 시간 예고
다음 시간에는 스트림 조작자와 포매팅에 대해 알아볼 거예요!
- 출력 형식을 예쁘게 만드는 스트림 조작자들
- setw, setprecision, setfill 등의 활용법
- 게임 스코어보드 같은 정렬된 출력 만들기
- 소수점, 진법, 정렬 등 다양한 포매팅 기법
“이진 파일로 더 빠르고 효율적인 데이터 저장을! 💾⚡”
Last updated on