C++ 기본기 연습 프로젝트: 아이템 사전 프로그램
1. 해시테이블 직접 구현
Pair → LinkedList(Node) → HashTable 구현
Pair.h
template<typename Key, typename Value>class Pair{public: Pair() = default; Pair(const Key& key, const Value& value) : key(key), value(value) {} const Key& GetKey() const { return key; } const Value& GetValue() const { return value; }private: Key key; Value value;};
Node.h
template<typename Key, typename Value>class LinkedList;template<typename Key, typename Value>class Node{ friend class LinkedList<Key, Value>;public: Node(const Key& key, const Value& value) : data(key, value), next(nullptr) {} const Pair<Key, Value>& GetData() const { return data; } Node* GetNext() const { return next; }private: Node* next; Pair<Key, Value> data;};
- 같은 파일 안이더라도 전방선언을 해줘야(line 1, 2)
friend class LinkedList가 가능하다
- 교안에선 doubly linked list로 구현돼있는데 난 단방향으로 했음
LinkedList.h
template<typename Key, typename Value>class LinkedList{public: ...private: Node<Key, Value>* head;};
Add()는 Node의 생성자로 동적할당한 새 노드를 헤드로 갈아끼움
void Add(const Key& key, const Value& value){ Node<Key, Value>* newNode = new Node<Key, Value>(key, value); newNode->next = head; head = newNode;}
Find()는 head를 타고 내려가면서 key 대조하기
Node<Key, Value>* Find(const Key& key) const{ Node<Key, Value>* current = head; while (current) { if (current->data.GetKey() == key) return current; current = current->next; } return nullptr;}
Remove()는 링크드리스트가 1뎁스인 경우를 고려해야해서 삭제해야하는 노드가 헤든지 아닌지로 나눠줬음
bool Remove(const Key& key){ if (head && head->data.GetKey() == key) { Node<Key, Value>* toDelete = head; head = head->next; delete toDelete; return true; } Node<Key, Value>* prev = head; Node<Key, Value>* current = head ? head->next : nullptr; while (current) { if (current->data.GetKey() == key) { prev->next = current->next; delete current; return true; } prev = current; current = current->next; } return false;}
Clear()는 무작정 head부터 지우면 연결노드를 방문할 수 없기 때문에 temp 테크닉으로 살려줌
void Clear(){ while (head) { Node<Key, Value>* temp = head; head = head->next; delete temp; }}
HashTable.h
class HashTable{public: HashTable(int capacity = 101) : capacity(capacity) { buckets.resize(capacity); } ~HashTable() { for (int i = 0; i < capacity; ++i) { buckets[i].Clear(); } } void Add(const std::string& name, const std::string& type, int value) Item* Find(const std::string& name) bool Remove(const std::string& name) void List() constprivate: int Hash(const std::string& key) const { unsigned int hash = 0; for (char c : key) { hash = hash * 31 + c; } return hash % capacity; }private: std::vector<LinkedList<std::string, Item>> buckets; int capacity;};
- 해시테이블의 핵심은 private으로 감춰진
Hash()함수
- 메시지 함수는 모두
int index = Hash(name); + ListTable API 호출이 전부임
2. 카드 브라우저 프로그램 붙이기
bool LoadFromFile(const std::string& filename){ std::ifstream file(filename); if (!file.is_open()) { return false; } std::string line; std::getline(file, line); while (std::getline(file, line)) { std::stringstream ss(line); std::string name, type, valueStr; std::getline(ss, name, ','); std::getline(ss, type, ','); std::getline(ss, valueStr, ','); int value = std::stoi(valueStr); Add(name, type, value); } file.close(); return true;}
- 카드 프로그램의 Parser 그대로 가져와
HashTable.h 메시지로 붙임
std::vector<Item> GetAllItems() const{ std::vector<Item> items; for (int i = 0; i < capacity; ++i) { Node<std::string, Item>* current = buckets[i].GetHead(); while (current) { items.push_back(current->GetData().GetValue()); current = current->GetNext(); } } return items;}
LinkedList 형태로 존재하는 데이터들을 flatten해주는 함수 추가
void RenderUI(const std::vector<Item>& items, int selected, int offset){ system("cls"); Util::PutCursorOnPosition(0, 0); std::cout << "=== 아이템 사전 (HashTable) ===\n"; std::cout << "[방향키: 이동] [ESC/Q: 명령어 모드]"; for (i = 0; i < 20; ++i) { int idx = (offset + i) % items.size(); std::cout << items[idx].name << " (" << items[idx].type << ")"; } if (!items.empty()) { const Item& item = items[selected]; Util::PutCursorOnPosition(40, 3); std::cout << "[ 아이템 상세 정보 ]"; Util::PutCursorOnPosition(40, 5); std::cout << "Name : " << item.name; Util::PutCursorOnPosition(40, 6); std::cout << "Type : " << item.type; Util::PutCursorOnPosition(40, 7); std::cout << "Value : " << item.value; }}void CommandMode(HashTable& itemDict){ system("cls"); std::cout << "=== 명령어 모드 ===" << "\n"; std::cout << "====================" << "\n" << "\n"; std::string line; while (true) { std::cout << "> "; std::getline(std::cin, line); std::stringstream ss(line); std::string command; ss >> command; if (command == "add") { if (ss >> name >> type >> value) { itemDict.Add(name, type, value); } } else if (command == "find") { ss >> name; Item* item = itemDict.Find(name); } else if (command == "remove") { ss >> name; if (itemDict.Remove(name)) else } else if (command == "list") { itemDict.List(); } else if (command == "ui") { return; // UI 모드로 복귀 } else if (command == "exit") { exit(0); } else { std::cout << "알 수 없는 명령어입니다." << "\n"; } }}
- 아이템 사전 순회 모드(UI 모드)용
RenderUI(), 아이템 사전 수정 모드용 CommandMode() 분리
그래픽스 프로그래밍
- StaticMesh
- mesh ↔ shader 1:1 구조
- StaticMesh 안에 여러 하위 Mesh 배열이 생김
- 게임에선 Material을 최대한 줄이는게 좋음(초당 60번 렌더 - 셰이더를 줄여야 함)
- Renderer::Submit
- Actor는 mesh와 shader를 전달만 하고
- Renderer는 전달받은 mesh와 shader를 그리기만 함
template<typename T, typename std::enable_if_t<std::is_base_of<Level, T>::value>>
- SFINAE(Substitution Failure Is Not An Error): 어려운 면접 문제에 간혹 등장
- 특정 타입을 부모로 강제하고 싶은데 템플릿 환경일 때의 방법 질문
- std::is_base_of 를 활용하는 것이 1차 답변 (살짝 부족)
- std::enable_if / std::enable_if_t 접목해서 의도적으로 템플릿 해석 실패하도록 유도해서 구문 오류가 발생하도록 하는 방법
- std::enable_shared_from_this
- Material == Shader + 속성(숫자값, 텍스처, ...)
- 셰이더를 미리 컴파일해서
.cso로 만들어놓고 vertexShaderObject로 가져와서 런타임에 사용(상용 엔진)
- 고도화: 리소스매니저 계층에서 딱 하나만 만들고 주소 공유