오늘은 vector를 사용해서 객체들을 관리하는 연금술 공방 시스템을 만들었다. 🧪 단순히 데이터를 추가하고 전체를 보는 것에서 나아가, 물약 이름이나 재료로 원하는 레시피를 찾아내는 검색 기능을 구현했다. 특히 cin으로 숫자를 받고 getline으로 문자열을 받을 때 생기는 입력 버퍼 문제를 해결하는 과정이 기억에 남는다. 이 과정을 통해 C++에서 사용자 입력을 다루는 법을 더 깊이 이해하게 되었다. 👍
vector에 클래스 객체 저장 및 관리하기for문을 이용한 중첩 데이터 구조 탐색cin과 getline 사용 시 발생하는 입력 버퍼 문제 해결#include <iostream>
#include <vector>
#include <string>
using namespace std;
// PotionRecipe 클래스: 하나의 물약 레시피 정보를 담는 클래스
class PotionRecipe {
public:
string potionName;
vector<string> ingredients;
// 생성자: 물약 이름과 재료 목록을 받아 객체를 초기화합니다.
PotionRecipe(const string& name, const vector<string>& ingredients)
: potionName(name), ingredients(ingredients) {
}
};
// AlchemyWorkshop 클래스: 레시피 목록을 관리하고 관련 기능을 제공하는 클래스
class AlchemyWorkshop {
private:
// PotionRecipe 객체들을 저장할 벡터(동적 배열)
vector<PotionRecipe> recipes;
public:
// 레시피를 추가하는 메서드
void addRecipe(const string& name, const vector<string>& ingredients) {
recipes.push_back(PotionRecipe(name, ingredients));
cout << ">> 새로운 레시피 '" << name << "'이(가) 추가되었습니다." << endl;
}
// 모든 레시피를 화면에 출력하는 메서드
void displayAllRecipes() const {
if (recipes.empty()) {
cout << "아직 등록된 레시피가 없습니다." << endl;
return;
}
cout << "\n--- [ 전체 레시피 목록 ] ---" << endl;
// for 루프를 사용해 저장된 모든 레시피를 순회합니다.
for (size_t i = 0; i < recipes.size(); ++i) {
cout << "- 물약 이름: " << recipes[i].potionName << endl;
cout << " > 필요 재료: ";
// 한 레시피에 포함된 모든 재료를 순회하며 출력합니다.
for (size_t j = 0; j < recipes[i].ingredients.size(); ++j) {
cout << recipes[i].ingredients[j];
if (j < recipes[i].ingredients.size() - 1) {
cout << ", ";
}
}
cout << endl;
}
cout << "---------------------------\n";
}
// --- [필수 기능] 물약 이름으로 레시피 검색 ---
// const 키워드: 이 함수는 멤버 변수(recipes)를 수정하지 않는다는 의미
void searchByPotionName(const string& name) const {
bool found = false; // 검색 성공 여부를 저장할 변수
cout << "\n--- [ '" << name << "' 물약 이름으로 검색 결과 ] ---" << endl;
// 모든 레시피를 처음부터 끝까지 확인합니다.
for (size_t i = 0; i < recipes.size(); ++i) {
// 현재 확인 중인 레시피의 이름(recipes[i].potionName)과
// 사용자가 입력한 이름(name)이 같은지 비교합니다.
if (recipes[i].potionName == name) {
// 이름이 같다면, 해당 레시피 정보를 출력합니다.
cout << "- 물약 이름: " << recipes[i].potionName << endl;
cout << " > 필요 재료: ";
for (size_t j = 0; j < recipes[i].ingredients.size(); ++j) {
cout << recipes[i].ingredients[j];
if (j < recipes[i].ingredients.size() - 1) {
cout << ", ";
}
}
cout << endl;
found = true; // 찾았다고 표시
break; // 이름은 중복되지 않으므로, 하나 찾으면 더 이상 찾을 필요가 없어 반복문을 종료합니다.
}
}
// for 루프가 끝날 때까지 found가 false라면, 한 번도 레시피를 찾지 못했다는 의미입니다.
if (!found) {
cout << "해당 이름의 레시피를 찾을 수 없습니다." << endl;
}
cout << "------------------------------------------------\n";
}
// --- [필수 기능] 재료로 레시피 검색 ---
void searchByIngredient(const string& ingredient) const {
bool found = false; // 검색 성공 여부를 저장할 변수
cout << "\n--- [ '" << ingredient << "' 재료 포함 레시피 검색 결과 ] ---" << endl;
// 1. 바깥쪽 for 루프: 모든 레시피를 하나씩 확인합니다.
for (size_t i = 0; i < recipes.size(); ++i) {
// 2. 안쪽 for 루프: 현재 레시피(recipes[i])에 포함된 모든 재료를 하나씩 확인합니다.
for (size_t j = 0; j < recipes[i].ingredients.size(); ++j) {
// 현재 확인 중인 재료(recipes[i].ingredients[j])가
// 사용자가 입력한 재료(ingredient)와 같은지 비교합니다.
if (recipes[i].ingredients[j] == ingredient) {
// 재료가 포함되어 있다면, 레시피 정보를 출력합니다.
cout << "- 물약 이름: " << recipes[i].potionName << endl;
found = true; // 찾았다고 표시
break; // 이 레시피에 해당 재료가 있다는 것을 확인했으니, 더 이상 이 레시피의 다른 재료를 볼 필요가 없습니다. 안쪽 루프만 탈출합니다.
}
}
}
// 바깥쪽 for 루프까지 모두 끝났을 때 found가 false라면, 어떤 레시피에서도 해당 재료를 찾지 못한 것입니다.
if (!found) {
cout << "해당 재료를 사용하는 레시피를 찾을 수 없습니다." << endl;
}
cout << "----------------------------------------------------\n";
}
};
// 프로그램의 실제 실행 흐름을 담당하는 main 함수
int main() {
AlchemyWorkshop myWorkshop;
// 테스트를 위한 기본 레시피 추가
myWorkshop.addRecipe("체력 회복 물약", { "붉은 약초", "정제수" });
myWorkshop.addRecipe("마나 회복 물약", { "푸른 약초", "정제수" });
myWorkshop.addRecipe("힘 증폭 물약", { "붉은 약초", "트롤의 피" });
while (true) {
cout << "⚗️ 연금술 공방 관리 시스템" << endl;
cout << "1. 레시피 추가" << endl;
cout << "2. 모든 레시피 출력" << endl;
cout << "3. 레시피 검색" << endl; // 검색 기능 메뉴 추가
cout << "4. 종료" << endl; // 종료는 4번으로 변경
cout << "선택: ";
int choice;
cin >> choice;
if (cin.fail()) {
cout << "잘못된 입력입니다. 숫자를 입력해주세요." << endl;
cin.clear();
cin.ignore(10000, '\n');
continue;
}
// choice 입력 후 버퍼에 남은 엔터키(\n)를 비워주기 위한 코드.
// 이것이 없으면 다음에 나올 getline이 바로 종료되어 버립니다.
cin.ignore(10000, '\n');
if (choice == 1) {
string name;
cout << "물약 이름: ";
getline(cin, name);
vector<string> ingredients_input;
string ingredient;
cout << "필요한 재료들을 입력하세요. (입력 완료 시 '끝' 입력)" << endl;
while (true) {
cout << "재료 입력: ";
getline(cin, ingredient);
if (ingredient == "끝") {
break;
}
ingredients_input.push_back(ingredient);
}
if (!ingredients_input.empty()) {
myWorkshop.addRecipe(name, ingredients_input);
}
else {
cout << ">> 재료가 입력되지 않아 레시피 추가를 취소합니다." << endl;
}
}
else if (choice == 2) {
myWorkshop.displayAllRecipes();
}
else if (choice == 3) {
// --- 검색 기능 구현 ---
int search_choice;
cout << "\n--- 검색 옵션 ---" << endl;
cout << "1. 물약 이름으로 검색" << endl;
cout << "2. 재료로 검색" << endl;
cout << "선택: ";
cin >> search_choice;
if (cin.fail()) {
cout << "잘못된 입력입니다. 숫자를 입력해주세요." << endl;
cin.clear();
cin.ignore(10000, '\n');
continue;
}
cin.ignore(10000, '\n'); // 숫자 입력 후 버퍼 비우기
if (search_choice == 1) {
string potion_name_to_search;
cout << "검색할 물약 이름: ";
getline(cin, potion_name_to_search);
// 입력값이 비어있지 않은지 간단히 확인
if (!potion_name_to_search.empty()) {
myWorkshop.searchByPotionName(potion_name_to_search);
}
else {
cout << "검색할 이름을 입력해주세요." << endl;
}
}
else if (search_choice == 2) {
string ingredient_to_search;
cout << "검색할 재료 이름: ";
getline(cin, ingredient_to_search);
if (!ingredient_to_search.empty()) {
myWorkshop.searchByIngredient(ingredient_to_search);
}
else {
cout << "검색할 재료를 입력해주세요." << endl;
}
}
else {
cout << "잘못된 검색 옵션입니다." << endl;
}
}
else if (choice == 4) {
cout << "공방 문을 닫습니다..." << endl;
break;
}
else {
cout << "잘못된 선택입니다. 다시 시도하세요." << endl;
}
}
return 0;
}
cin으로 받고 나서 물약 이름을 getline으로 받으려고 했는데, 입력이 그냥 휙 넘어가 버렸다! 😫 알고 보니 cin이 숫자만 가져가고 엔터키(\n)를 버퍼에 남겨둬서, 다음에 오는 getline이 그 엔터키를 보고 '아, 입력 끝났구나' 하고 바로 종료된 것이었다. cin 다음에 cin.ignore() 한 줄을 넣어 버퍼를 비워주는 걸로 겨우 해결했다.found 변수 위치를 잘못 잡아서, 검색 결과가 없는데도 있다고 나오는 경우가 있었다. 🤦♂️ for 루프 안에서 bool found = false;를 선언했더니, 매번 초기화돼서 마지막 결과를 제대로 반영하지 못했다. found는 반복문이 시작하기 전에 딱 한 번 선언하고 false로 초기화해야 한다는 기본을 잠시 잊었다.recipes.ingredients처럼 vector에서 바로 멤버 변수에 접근하려고 했다. 당연히 에러가 났다. 😂 recipes는 벡터니까, recipes[i]로 특정 PotionRecipe 객체를 먼저 선택하고, 그 다음에 .ingredients[j] 와 같이 접근해야 하는 거였다. 객체와 배열의 관계를 아직 헷갈리는 것 같다.| 개념 | 설명 | 비고 |
|---|---|---|
| 생성자 초기화 리스트 | 객체 생성 시 멤버 변수를 : 뒤에서 직접 초기화하는 방식. 대입보다 효율적이며 반드시 필요한 경우도 있음 | 문법이 낯설고, 생성 시점 차이를 이해해야 함 |
| const 메서드 | 메서드 뒤에 const를 붙이면 멤버 변수 변경 금지 보장. 읽기 전용 함수임을 명확히 함 | 객체 안정성 확보, 실수 방지 |
| cin 버퍼 처리 | cin >> 뒤에 남은 \n이 getline에 영향을 주므로 cin.ignore()로 버퍼를 비워줘야 함 | 초보가 자주 겪는 입력 건너뛰기 오류 방지 |
| 입력 오류 처리 | cin.fail()로 잘못된 입력 감지 후 cin.clear() + cin.ignore()로 복구 | 견고한 프로그램 작성에 필수 |
| 이중 for문 탐색 | 레시피 → 재료 순으로 모두 순회하며 검색. O(N*M) 시간 복잡도 발생 | 자료구조/알고리즘 최적화로 발전 가능 |
| 객체 벡터 관리 | vector<PotionRecipe>처럼 객체를 직접 보관하면 복사/소멸자 호출 규칙 이해 필요 | C++ 객체 생명주기와 메모리 관리 개념 연계 |