Topic 4: 문자열 스트림과 변환 - 문자열 마법사 되기 🔄
🎯 학습 목표
- stringstream의 개념과 동작 원리를 이해할 수 있다
- 문자열과 숫자 간의 자유로운 변환을 할 수 있다
- 복잡한 문자열을 파싱하고 데이터를 추출할 수 있다
- istringstream과 ostringstream의 차이점을 구분할 수 있다
- 실제 게임 명령어 시스템을 구현할 수 있다
📝 메모장과 stringstream
stringstream = 메모리 속 가상 메모장 📱💭
일반적인 파일 입출력:
// 실제 파일에 쓰기
ofstream file("data.txt");
file << "Hello World";stringstream 사용:
// 메모리 속 가상 파일에 쓰기
stringstream ss;
ss << "Hello World";
string result = ss.str(); // "Hello World"메모장 비유:
- 파일 스트림 = 실제 종이 노트에 쓰기 📔
- stringstream = 휴대폰 메모장에 쓰기 📱
- 더 빠르고, 임시 작업에 완벽! ⚡
🎪 stringstream의 3형제
1. stringstream - 읽기/쓰기 겸용 🔄
stringstream ss;
ss << "data"; // 쓰기
string result;
ss >> result; // 읽기2. ostringstream - 쓰기 전용 ✍️
ostringstream oss;
oss << "output data";
string result = oss.str();3. istringstream - 읽기 전용 📖
string data = "input data";
istringstream iss(data);
string word;
iss >> word;🔢 문자열 ↔ 숫자 변환의 마법
숫자를 문자열로 변환
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
// 다양한 타입을 문자열로 변환
int playerLevel = 25;
double playerHealth = 87.5;
bool isAlive = true;
stringstream ss;
// 숫자들을 문자열로 변환
ss << "플레이어 레벨: " << playerLevel << " | ";
ss << "체력: " << playerHealth << "% | ";
ss << "상태: " << (isAlive ? "생존" : "사망");
string gameStatus = ss.str();
cout << "🎮 " << gameStatus << endl;
// 또 다른 방법: 개별 변환
ostringstream scoreStream;
scoreStream << "점수: " << 98500 << "점";
string scoreText = scoreStream.str();
cout << "🏆 " << scoreText << endl;
return 0;
}실행 결과:
🎮 플레이어 레벨: 25 | 체력: 87.5% | 상태: 생존
🏆 점수: 98500점문자열을 숫자로 변환
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
// 사용자 입력 시뮬레이션
string userInput = "25 87.5 true";
istringstream iss(userInput);
int level;
double health;
string aliveStr;
// 문자열에서 데이터 추출
iss >> level >> health >> aliveStr;
bool isAlive = (aliveStr == "true");
cout << "📊 파싱 결과:" << endl;
cout << "레벨: " << level << endl;
cout << "체력: " << health << "%" << endl;
cout << "생존 여부: " << (isAlive ? "생존 😊" : "사망 💀") << endl;
return 0;
}🎮 실전 예제: 게임 세이브 데이터 파서
복잡한 세이브 데이터 파싱하기
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct PlayerData {
string name;
int level;
double health;
int gold;
vector<string> inventory;
};
class SaveDataParser {
public:
// 플레이어 데이터를 문자열로 직렬화
static string serialize(const PlayerData& player) {
ostringstream oss;
// 기본 정보
oss << player.name << "|" << player.level << "|"
<< player.health << "|" << player.gold << "|";
// 인벤토리 아이템들 (콤마로 구분)
for (size_t i = 0; i < player.inventory.size(); i++) {
oss << player.inventory[i];
if (i < player.inventory.size() - 1) {
oss << ",";
}
}
return oss.str();
}
// 문자열을 플레이어 데이터로 파싱
static PlayerData deserialize(const string& data) {
PlayerData player;
istringstream iss(data);
string token;
// | 구분자로 기본 정보 파싱
getline(iss, player.name, '|');
getline(iss, token, '|');
player.level = stoi(token); // string to int
getline(iss, token, '|');
player.health = stod(token); // string to double
getline(iss, token, '|');
player.gold = stoi(token);
// 인벤토리 파싱
string inventoryData;
getline(iss, inventoryData);
if (!inventoryData.empty()) {
istringstream inventoryStream(inventoryData);
string item;
while (getline(inventoryStream, item, ',')) {
player.inventory.push_back(item);
}
}
return player;
}
// 플레이어 정보 출력
static void printPlayer(const PlayerData& player) {
cout << "⚔️ =========================" << endl;
cout << " 플레이어: " << player.name << endl;
cout << " 레벨: " << player.level << " 📊" << endl;
cout << " 체력: " << player.health << "% ❤️" << endl;
cout << " 골드: " << player.gold << " 💰" << endl;
cout << " 인벤토리: ";
if (player.inventory.empty()) {
cout << "(비어있음) 📦";
} else {
for (size_t i = 0; i < player.inventory.size(); i++) {
cout << player.inventory[i];
if (i < player.inventory.size() - 1) {
cout << ", ";
}
}
cout << " 🎒";
}
cout << endl;
cout << "⚔️ =========================" << endl;
}
};
int main() {
// 원본 플레이어 데이터
PlayerData hero;
hero.name = "DragonSlayer";
hero.level = 42;
hero.health = 87.5;
hero.gold = 1250;
hero.inventory = {"강철검", "체력포션", "마법반지", "용의비늘"};
cout << "🎮 원본 플레이어 데이터:" << endl;
SaveDataParser::printPlayer(hero);
// 직렬화 (객체 → 문자열)
string saveData = SaveDataParser::serialize(hero);
cout << "\n💾 직렬화된 데이터:" << endl;
cout << saveData << endl;
// 파일에 저장 시뮬레이션
cout << "\n📁 파일에 저장 중..." << endl;
cout << "✅ 저장 완료!" << endl;
// 역직렬화 (문자열 → 객체)
cout << "\n📂 데이터 로딩 중..." << endl;
PlayerData loadedPlayer = SaveDataParser::deserialize(saveData);
cout << "\n🔄 로드된 플레이어 데이터:" << endl;
SaveDataParser::printPlayer(loadedPlayer);
return 0;
}실행 결과:
🎮 원본 플레이어 데이터:
⚔️ =========================
플레이어: DragonSlayer
레벨: 42 📊
체력: 87.5% ❤️
골드: 1250 💰
인벤토리: 강철검, 체력포션, 마법반지, 용의비늘 🎒
⚔️ =========================
💾 직렬화된 데이터:
DragonSlayer|42|87.5|1250|강철검,체력포션,마법반지,용의비늘
📁 파일에 저장 중...
✅ 저장 완료!
📂 데이터 로딩 중...
🔄 로드된 플레이어 데이터:
⚔️ =========================
플레이어: DragonSlayer
레벨: 42 📊
체력: 87.5% ❤️
골드: 1250 💰
인벤토리: 강철검, 체력포션, 마법반지, 용의비늘 🎒
⚔️ =========================🎯 고급 예제: 게임 명령어 시스템
실시간 게임 명령어 처리기
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
using namespace std;
class GameCommandSystem {
private:
map<string, int> playerStats;
vector<string> inventory;
public:
GameCommandSystem() {
// 기본 스탯 초기화
playerStats["level"] = 1;
playerStats["hp"] = 100;
playerStats["mp"] = 50;
playerStats["gold"] = 100;
}
// 명령어 파싱 및 실행
void processCommand(const string& command) {
istringstream iss(command);
string action;
iss >> action; // 첫 번째 단어가 액션
cout << "🎮 명령어: " << command << endl;
if (action == "stat") {
showStats();
}
else if (action == "add") {
string type;
int amount;
iss >> type >> amount;
addStat(type, amount);
}
else if (action == "buy") {
string item;
int price;
iss >> item >> price;
buyItem(item, price);
}
else if (action == "inventory" || action == "inv") {
showInventory();
}
else if (action == "save") {
saveGame();
}
else if (action == "load") {
string saveData;
// 실제로는 파일에서 읽어야 하지만, 여기서는 직접 입력
getline(iss, saveData);
loadGame(saveData);
}
else if (action == "help") {
showHelp();
}
else {
cout << "❌ 알 수 없는 명령어입니다. 'help'를 입력해보세요." << endl;
}
cout << endl;
}
private:
void showStats() {
cout << "📊 === 캐릭터 스탯 ===" << endl;
cout << "레벨: " << playerStats["level"] << " ⭐" << endl;
cout << "HP: " << playerStats["hp"] << " ❤️" << endl;
cout << "MP: " << playerStats["mp"] << " 💙" << endl;
cout << "골드: " << playerStats["gold"] << " 💰" << endl;
}
void addStat(const string& type, int amount) {
if (playerStats.find(type) != playerStats.end()) {
playerStats[type] += amount;
cout << "✅ " << type << "이(가) " << amount << " 증가했습니다!" << endl;
cout << "현재 " << type << ": " << playerStats[type] << endl;
} else {
cout << "❌ 알 수 없는 스탯입니다: " << type << endl;
}
}
void buyItem(const string& item, int price) {
if (playerStats["gold"] >= price) {
playerStats["gold"] -= price;
inventory.push_back(item);
cout << "✅ " << item << "을(를) " << price << "골드에 구매했습니다!" << endl;
cout << "남은 골드: " << playerStats["gold"] << " 💰" << endl;
} else {
cout << "❌ 골드가 부족합니다! (필요: " << price << ", 보유: "
<< playerStats["gold"] << ")" << endl;
}
}
void showInventory() {
cout << "🎒 === 인벤토리 ===" << endl;
if (inventory.empty()) {
cout << "(비어있음)" << endl;
} else {
for (size_t i = 0; i < inventory.size(); i++) {
cout << (i + 1) << ". " << inventory[i] << endl;
}
}
}
void saveGame() {
ostringstream saveData;
// 스탯 저장
saveData << playerStats["level"] << ","
<< playerStats["hp"] << ","
<< playerStats["mp"] << ","
<< playerStats["gold"] << "|";
// 인벤토리 저장
for (size_t i = 0; i < inventory.size(); i++) {
saveData << inventory[i];
if (i < inventory.size() - 1) {
saveData << ";";
}
}
string saveString = saveData.str();
cout << "💾 게임 저장 완료!" << endl;
cout << "세이브 데이터: " << saveString << endl;
}
void loadGame(const string& saveData) {
if (saveData.empty()) {
cout << "❌ 세이브 데이터가 비어있습니다!" << endl;
return;
}
istringstream iss(saveData);
string statsData, inventoryData;
// | 구분자로 스탯과 인벤토리 분리
getline(iss, statsData, '|');
getline(iss, inventoryData);
// 스탯 로드
istringstream statsStream(statsData);
string token;
if (getline(statsStream, token, ',')) {
playerStats["level"] = stoi(token);
}
if (getline(statsStream, token, ',')) {
playerStats["hp"] = stoi(token);
}
if (getline(statsStream, token, ',')) {
playerStats["mp"] = stoi(token);
}
if (getline(statsStream, token, ',')) {
playerStats["gold"] = stoi(token);
}
// 인벤토리 로드
inventory.clear();
if (!inventoryData.empty()) {
istringstream invStream(inventoryData);
string item;
while (getline(invStream, item, ';')) {
inventory.push_back(item);
}
}
cout << "📂 게임 로드 완료!" << endl;
}
void showHelp() {
cout << "🆘 === 도움말 ===" << endl;
cout << "stat - 캐릭터 스탯 보기" << endl;
cout << "add [스탯] [수치] - 스탯 증가 (예: add hp 20)" << endl;
cout << "buy [아이템] [가격] - 아이템 구매 (예: buy 검 100)" << endl;
cout << "inv 또는 inventory - 인벤토리 보기" << endl;
cout << "save - 게임 저장" << endl;
cout << "load [데이터] - 게임 로드" << endl;
cout << "help - 도움말" << endl;
}
};
int main() {
GameCommandSystem game;
string command;
cout << "🎮 === 게임 명령어 시스템 시작 === 🎮" << endl;
cout << "'help'를 입력하면 사용 가능한 명령어를 볼 수 있습니다." << endl;
cout << "'quit'을 입력하면 종료됩니다." << endl << endl;
while (true) {
cout << "명령어 입력 > ";
getline(cin, command);
if (command == "quit" || command == "exit") {
cout << "👋 게임을 종료합니다!" << endl;
break;
}
if (!command.empty()) {
game.processCommand(command);
}
}
return 0;
}사용 예시:
명령어 입력 > stat
📊 === 캐릭터 스탯 ===
레벨: 1 ⭐
HP: 100 ❤️
MP: 50 💙
골드: 100 💰
명령어 입력 > add hp 50
✅ hp이(가) 50 증가했습니다!
현재 hp: 150
명령어 입력 > buy 강철검 80
✅ 강철검을(를) 80골드에 구매했습니다!
남은 골드: 20 💰
명령어 입력 > inv
🎒 === 인벤토리 ===
1. 강철검
명령어 입력 > save
💾 게임 저장 완료!
세이브 데이터: 1,150,50,20|강철검🔄 문자열 조작 고급 테크닉
CSV 데이터 파싱하기
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
using namespace std;
struct GameRecord {
string playerName;
int score;
string date;
double playTime;
};
vector<GameRecord> parseCSV(const string& csvData) {
vector<GameRecord> records;
istringstream dataStream(csvData);
string line;
// 헤더 라인 건너뛰기
getline(dataStream, line);
while (getline(dataStream, line)) {
istringstream lineStream(line);
string token;
GameRecord record;
// 플레이어명
getline(lineStream, record.playerName, ',');
// 점수
getline(lineStream, token, ',');
record.score = stoi(token);
// 날짜
getline(lineStream, record.date, ',');
// 플레이 시간
getline(lineStream, token, ',');
record.playTime = stod(token);
records.push_back(record);
}
return records;
}
int main() {
string csvData = R"(PlayerName,Score,Date,PlayTime
DragonSlayer,95000,2024-03-15,2.5
MagicMaster,87500,2024-03-14,1.8
SwordHero,76200,2024-03-13,3.2
ArrowAce,68900,2024-03-12,2.1)";
cout << "📊 CSV 데이터 파싱 결과:" << endl;
cout << "========================" << endl;
vector<GameRecord> records = parseCSV(csvData);
for (const auto& record : records) {
cout << "플레이어: " << record.playerName << endl;
cout << "점수: " << record.score << "점 🏆" << endl;
cout << "날짜: " << record.date << " 📅" << endl;
cout << "플레이 시간: " << record.playTime << "시간 ⏰" << endl;
cout << "------------------------" << endl;
}
return 0;
}💡 핵심 정리
- stringstream: 문자열을 스트림처럼 다루는 클래스
- ostringstream: 출력 전용, 문자열 생성에 특화
- istringstream: 입력 전용, 문자열 파싱에 특화
- str(): stringstream의 내용을 string으로 반환
- getline(stream, str, delimiter): 구분자 단위로 문자열 분리
- stoi(), stod(): 문자열을 숫자로 변환하는 함수
- 직렬화: 객체를 문자열로, 역직렬화: 문자열을 객체로
✅ 실습 체크리스트
🚀 다음 시간 예고
Unit-14 파일 입출력과 스트림을 모두 마쳤습니다! 🎉 다음 단원에서는 새로운 주제로 여러분과 만날 예정이에요.
지금까지 배운 내용들:
- 📝 파일 스트림 기초: 데이터를 영구 저장하는 방법
- 💾 이진 파일과 직렬화: 효율적인 데이터 저장 기법
- 🎨 스트림 조작자와 포매팅: 아름다운 출력 만들기
- 🔄 문자열 스트림과 변환: 문자열 처리의 고수 되기
“문자열 스트림으로 데이터 처리의 달인이 되었습니다! 🧙♂️✨”
Last updated on