Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료

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