게임 개발/학습 일지

C++ 기초 - STL(Standard Template Library)

이개 2026. 4. 9. 09:39

STL

  • 메모리 자동 관리가 제공되는 자료구조 라이브러리.
  • 템플릿 프로그래밍 기반이라, 다양한 자료형의 데이터들을 저장.
  • STL 자료구조에는 벡터(Vector) / 맵(Map) / 셋(Set) / 스택(Statck) / 큐(Queue) / 리스트(List) 등이 있음.
  • 벡터와 맵 정도만 쓰이고, 셋이 아주 가끔 쓰이는 편.
  • 다른 말로는 컨테이너라고도 함.

 

STL 대체품

  • 언리얼 엔진 객체들의 전용 컨테이너
    • TArray, TMap, TMultiMap, Tset
  • EA의 EASTL
    • STL과 호환
    • 메모리 등을 고친 컨테이너

 

벡터(Vector)

  • 데이터 개수가 증가함에 따라 자동으로 메모리를 관리해주는 배열
  • 동적 배열
  • 배열이기 때문에 그 안에 저장된 모든 요소들은 연속된 메모리 공간에 위치
  • 어떤 요소에도 임의로 접근(Random Access)가 가능

 

push_back()

벡터의 맨 뒤에 요소를 추가

Scores.push_back(30);
Names.push_back("Navi");
MyCats.push_back(NewCat);

 

pop_back()

벡터의 맨 뒤에 있는 요소를 제거

비어있는 벡터에 pop_back() 함수 호출하면 런타임 오류가 발생할 수 있음.

Scores.pop_back();

 

 

capacity()

저장 가능한 최대 요소 개수를 반환

 

size()

현재 저장된 요소 개수를 반환

 

reserve(size_t Newcapacity)

벡터의 현재 capacity를 수정.

_Newcapacity가 기존 capacity보다 작은 경우: 아무일도 발생하지 않음.

_Newcapacity가 기존 capacity보다 큰 경우: 메모리 재할당 발생.

값 초기화는 해주지 않음.

Scores.reserve(10u); // 10u는 unsigned int

 

벡터의 size가 커지다가 capacity를 넘어서게 되면 더 넓은 메모리를 동적 할당하고, 기존 데이터를 모두 복사한다.

불필요한 재할당 및 복사를 막으려면 처음부터 capacity를 크게 잡아야한다.

이때 사용하는 함수가 reserve()이다.

 

// Main.cpp

#include <iostream>
#include <vector>

int main()
{
	std::vector<int> Scores;
	Scores.reserve(8u);

	Scores.push_back(100);
	Scores.push_back(98);

	std::cout << "Current capacity: " << Scores.capacity() << std::endl;
	std::cout << "Current size: " << Scores.size() << std::endl;
	for (size_t i = 0; i < Scores.size(); ++i)
	{
		std::cout << "Scores[" << i << "] : " << Scores[i] << std::endl;
	}

	Scores.pop_back();

	std::cout << "Current capacity: " << Scores.capacity() << std::endl;
	std::cout << "Current size: " << Scores.size() << std::endl;
	for (size_t i = 0; i < Scores.size(); ++i)
	{
		std::cout << "Scores[" << i << "] : " << Scores[i] << std::endl;
	}

	// delete[] Scores; 구문이 필요없습니다.
	// Scores는 STL Vector이므로, 내부적으로 메모리 해제도 해줍니다.

	return 0;
}

 

vector 자체는 동적할당이지만, 내부적으로 메모리 해제를 해주므로 delete[] v 구문은 필요없다.

 

. u 접미사의 의미

  • 4: 기본적으로 int (signed int, 부호가 있는 정수) 타입.
  • 4u: unsigned int 타입입. 값은 똑같이 4지만, 메모리에서 데이터를 해석하는 방식이 "부호가 없음"으로 고정.

왜 reserve에서 4u를 쓰는가? (타입 일치)

std::vector::reserve(size_type n) 함수는 인자로 size_type을 받는다. C++ 표준 라이브러리에서 이 타입은 보통 unsigned int 또는 size_t와 같은 부호 없는 정수형. 아주 엄격한 컴파일러 설정에서는 "signed/unsigned mismatch" 경고를 낼 수 있는데, 4u라고 써주면 이런 경고를 원천 차단

 

 

 

resize(const size_t _Newsize)

벡터의 현재 size를 수정한다. 새로 생긴 데이터는 0에 준하는 값으로 초기화된다.

  1. _Newsize가 기존 size보다 작으면 초과분을 삭제한다.
  2. _Newsize가 size보다 크고, capacity보다는 작으면 정상 추가된다.
  3. _Newsize가 기존 capacity보다 크면 메모리 재할당이 발생한다.

 

벡터는 

 

clear()

벡터의 내용을 전부 지운다.

size는 0이 되고 capacity는 변하지 않는다.

Scores.clear();

 

 

assign(size_t n, T x);

n개의 값을 벡터에 대입한다.

std::vector<int> D1Vector;
D1Vector.assign(16u, 100); // 16 size의 공간

std::vector<std::vector<float>> D2Vector;
D2Vector.assign(4u, vector<float>(8u, 0.f); // 4 x 8 2차원 배열을 모두 0.f로 초기화

 

 

vector와 객체

기본 자료형 외에 사용자 정의 자료형(클래스)의 객체를 저장하는 벡터

 

// Main.cpp

#include <iostream>
#include <vector>

class Score
{
public:
	Score(int InID, float InValue)
		: ID(InID)
		, Value(InValue)
	{
	}

public:
	int ID;

	float Value;

};

void PrintScores(const std::vector<Score>& InScores);

int main()
{
	std::vector<Score> Scores;
	Scores.reserve(2);
	Scores.push_back(Score(100, 95.f));
	Scores.push_back(Score(110, 70.f));

	PrintScores(Scores);

	std::vector<Score> ClonedScores = Scores;
	ClonedScores[0].Value = 100.f;

	PrintScores(ClonedScores);

	return 0;
}

void PrintScores(const std::vector<Score>& InScores)
{
	std::cout << "Current capacity: " << InScores.capacity() << std::endl;
	std::cout << "Current size: " << InScores.size() << std::endl;
	for (size_t i = 0; i < InScores.size(); ++i)
	{
		std::cout << "InScores[" << i << "] : " << InScores[i].ID << ", " << InScores[i].Value << std::endl;
	}
}

 

 

객체를 보관하는 벡터의 동작

std::vector<Score> Scores;
Scores.reserve(2);
Scores.push_back(Score(100, 95.f));
Scores.push_back(Score(110, 70.f));

Scores.push_back(Score(200, 98.f));
	// 위 코드를 실행 할 때 capacity가 부족하고, 재할당과 복사가 일어납니다.

 

 

객체의 포인터를 보관하는 벡터

// Main.cpp

#include <iostream>
#include <vector>

class Score
{
public:
	Score(int InID, float InValue)
		: ID(InID)
		, Value(InValue)
	{
	}

public:
	int ID;

	float Value;

};

void PrintScores(const std::vector<Score*>& InScores);

int main()
{
	std::vector<Score*> Scores;
	Scores.reserve(2);
	Scores.push_back(new Score(100, 95.f));
	Scores.push_back(new Score(110, 70.f));

	PrintScores(Scores);

	std::vector<Score*> ClonedScores = Scores;
	ClonedScores[0]->Value = 100.f;

	PrintScores(Scores);
	PrintScores(ClonedScores);
	
	size_t Size = Scores.size();
	for (size_t i = 0; i < Size; ++i)
	{
		if (Scores[i] != nullptr)
		{
			delete Scores[i]; // Scores 벡터는 메모리 주소값만 관리할 뿐, 실제 객체는 사용자가 delete 해야함.
			Scores[i] = nullptr;
		}
	}
	ClonedScores.clear();
	Scores.clear();

	return 0;
}

void PrintScores(const std::vector<Score*>& InScores)
{
	std::cout << "Current capacity: " << InScores.capacity() << std::endl;
	std::cout << "Current size: " << InScores.size() << std::endl;
	for (size_t i = 0; i < InScores.size(); ++i)
	{
		std::cout << "InScores[" << i << "] : " << InScores[i]->ID << ", " << InScores[i]->Value << std::endl;
	}
}

 

 

객체의 포인터를 보관하는 벡터의 동작

std::vector<Score*> Scores;
Scores.reserve(2);
Scores.push_back(new Score(100, 95.f));
Scores.push_back(new Score(110, 70.f));
Scores.push_back(new Score(200, 98.f));
	// 마지막 줄을 실행 할 때 capacity가 부족하고, 재할당 및 복사가 발생합니다.

size_t Size = Scores.size();
for (size_t i = 0; i < Size; ++i)
{
	if (Scores[i] != nullptr)
	{
		delete Scores[i];
		Scores[i] = nullptr;
	}
}
Scores.clear();

 

 

벡터의 장점

인덱스를 통해 O(1) 시간 복잡도로 임의 접근이 가능하다.

 

벡터의 단점

중간에 요소 삽입 및 삭제는 느리다. 요소를 한 칸씩 밀거나 당겨야하기 때문이다.

  • 맨 앞에 요소를 추가하거나 중간 요소를 삭제한다면 복사 문제 발생
    • 원래 있던 요소들을 한칸씩 미뤄서 복사를 해줘야한다.
  • 만약 가득 찬 vector에 insert가 이루어진다고 하면 새로운 메모리에 재할당한 뒤에 원래 있던 데이터를 복사하고, 첫번째에 값을 insert() 하게 되어 비용이 많이 든다.

 

 

iterator

operator[](size_t InIndex);

지정된 위치(InIndex)의 요소를 참조로 반환

Scores[3] = 7;

std::cout << Names[3] << " ";

std::cout << MyCats[2].GetAge() << " ";

 

 

Vector와 [] 연산자

// Main.cpp

#include <iostream>
#include <vector>

int main()
{
	std::vector<int> Scores;
	Scores.reserve(8);

	Scores.push_back(100);
	Scores.push_back(98);

	Scores.pop_back();
	
	if (Scores.empty() == true)
	{
		return 0;
	}

	int ScoreCount = static_cast<int>(Scores.size() - 1u);
	for (int i = ScoreCount; i >= 0; --i)
	{
		std::cout << Scores[i] << std::endl;
	}

	return 0;
}

 

 

[] 연산자는 특정 컨테이너만 쓸 수 있다. std:list, std::set은 operator[]가 없다.

단, 반복자(iterator)를 이용하면 모든 STL 컨테이너를 순회할 수 있다.

 

반복자(iterator)

Iterator는 컨테이너 내부 요소를 가리키며 이동하는 객체라고 할 수 있다. 포인터처럼 동작하는 순회 도구이다

for (auto it = v.begin(); it != v.end(); ++it) {
    cout << *it << endl;
}
  • it → 현재 요소를 가리킴
  • *it → 값 접근
  • ++it → 다음 요소로 이동

👉 포인터처럼 *, ++ 사용

 

index 방식은 배열과 벡터에서만 사용할 수 있고, iterator는 모든 컨테이너에서 사용할 수 있다. 

map에서 m["fruit"] 같은 형식을 쓸 수는 있지만, 이는 fruit이라는 키를 가진 값을 탐색한다는 의미로 저장 위치인 index와는 다르다.

 

  • begin() : 자료구조의 첫 번째 요소를 가리키는 반복자 반환
  • end(): 자료구조의 마지막 요소 바로 뒤의 요소를 가리키는 반복자 반환
    • 마지막 요소 바로 뒤 요소를 가리키는 이유
      • 비어있는 컨테이너에서는 마지막 요소가 없다. 마지막 요소를 가리킬 수가 없는 경우에는 마지막 요소 바로 뒤를 가리킨다는 개념을 적용해서 첫 부분을 가리킬 수 있게 된다. begin() == end() 조건식으로 비어있는 컨테이너를 표현할 수 있다.
      • 컨테이너에서 원하는 데이터를 찾지 못한 경우에 end iterator를 반환하면 의미가 적절하다.
// vector<자료형>::iterator <변수명>

std::vector<int>::iterator BeginIter = Scores.begin();
std::vector<int>::iterator EndIter = Scores.end();

 

 

 

역방향 반복자

  • rbegin(): vector의 마지막 요소를 가리키는 역방향 반복자를 반환
  • rend(): vector의 첫 번째 요소 앞을 가리키는 역방향 반복자를 반환
// Main.cpp

#include <iostream>
#include <vector>

void PrintScores(const std::vector<int>& InScores);
void PrintScoresInReverse(const std::vector<int>& InScores);

int main()
{
	std::vector<int> Scores;
	Scores.reserve(8);

	Scores.push_back(100);
	Scores.push_back(98);
	Scores.push_back(70);
	Scores.push_back(86);
	Scores.push_back(65);

	PrintScores(Scores);

	Scores.pop_back();
	Scores.pop_back();

	PrintScoresInReverse(Scores);

	Scores.reserve(7);

	PrintScoresInReverse(Scores);

	return 0;
}

void PrintScores(const std::vector<int>& InScores)
{
	// InScores가 const이므로 const_iterator를 사용해야 한다.
	std::vector<int>::const_iterator LeftIter = InScores.begin();
	std::vector<int>::const_iterator RightIter = InScores.end();
	while (LeftIter != RightIter)
	{
		std::cout << *LeftIter << std::endl;
		++LeftIter;
	}
	std::cout << "Current capacity: " << InScores.capacity() << std::endl;
	std::cout << "Current size: " << InScores.size() << std::endl;

	std::cout << std::endl;
}

void PrintScoresInReverse(const std::vector<int>& InScores)
{
	// InScores가 const이므로 const_reverse_iterator를 사용해야 한다.
	std::vector<int>::const_reverse_iterator RightIter = InScores.rbegin();
	std::vector<int>::const_reverse_iterator LeftIter = InScores.rend();
	while (RightIter != LeftIter)
	{
		std::cout << *RightIter << std::endl;
		++RightIter;
	}
	std::cout << "Current capacity: " << InScores.capacity() << std::endl;
	std::cout << "Current size: " << InScores.size() << std::endl;
	std::cout << std::endl;
}

 

 

포인터 객체 벡터에서의 delete

// Main.cpp

#include <iostream>
#include <vector>

class Score
{
public:
	Score(int InID, float InValue)
		: ID(InID)
		, Value(InValue)
	{
	}

public:
	int ID;
	
	float Value;

};

int main()
{
	std::vector<Score*> Scores;
	Scores.reserve(4u);
	Scores.push_back(new Score(100, 95.f));
	Scores.push_back(new Score(110, 70.f));
	Scores.push_back(new Score(200, 98.f));

	std::cout << "Scores's size: " << Scores.size() << std::endl;
	std::cout << "Scores's capacity: " << Scores.capacity() << std::endl;

	{
		std::vector<Score*>::iterator LeftIter = Scores.begin();
		while (LeftIter != Scores.end())
		{
			if (110 == (*LeftIter)->ID)
			{
				delete *LeftIter;
				LeftIter = Scores.erase(LeftIter);
			}
			else
			{
				++LeftIter;
			}
		}
	}

	std::cout << "Scores's size: " << Scores.size() << std::endl;
	std::cout << "Scores's capacity: " << Scores.capacity() << std::endl;
	
	{
		std::vector<Score*>::iterator LeftIter = Scores.begin();
		std::vector<Score*>::iterator RightIter = Scores.end();
		while (LeftIter != RightIter)
		{
			delete *LeftIter;
			++LeftIter;
		}

		Scores.clear();
	}

	std::cout << "Scores's size: " << Scores.size() << std::endl;
	std::cout << "Scores's capacity: " << Scores.capacity() << std::endl;

	return 0;
}

 

 

 

Pair

두 데이터를 한 단위로 저장하는 구조

// std::pair<key 자료형, value 자료형>

std::pair<int, float> Score01(100, 90.f);
std::pair<int, float> Score02(110, 70.f);

 

Map

Key와 Value의 쌍들을 저장. 키와 값이 매핑되어 있어 Map이라고 부른다.

이진탐색트리 기반이고, 키는 중복될 수 없다.

// std::map<key 자료형, value 자료형> 변수명;
std::map<int, float> ScoreMap;

 

 

중복값을 허용하는 맵으로 MultiMap이 있다. 

#include <iostream>
#include <map>
using namespace std;

int main() {
    multimap<string, int> mm;

    mm.insert({"apple", 1});
    mm.insert({"banana", 2});
    mm.insert({"apple", 3}); // 같은 키 또 추가

    for (auto& p : mm) {
        cout << p.first << " : " << p.second << endl;
    }
}
auto range = mm.equal_range("apple");

for (auto it = range.first; it != range.second; ++it) {
    cout << it->second << endl;
}

// 결과
// 1
// 2

equal_range = 해당 키의 전체 범위 반환

 

std::pair<iterator, bool> insert 함수

새 요소를 맵에 삽입하고, 반복자와 bool 값을 한 쌍으로 반환한다.

반복자는 요소를 가리키고 bool 값은 삽입 결과를 알려준다.

ScoreMap.insert(std::pair<int, float>(100, 95.f));
	// (iterator, true)를 반환합니다.
	
ScoreMap.insert(std::pair<int, float>(100, 70.f)); 
	// Map 컨테이너에 key가 중복으로 삽입되었으므로, (iterator, false)를 반환합니다.

 

operator[]() 연산자

key에 대응하는 값을 참조로 반환

맵에 키가 없으면 새 요소 삽입, 이미 있으면 덮어쓰기

std::map<int, float> ScoreMap;
ScoreMap[100] = 95.f; // 새 요소를 삽입합니다.
ScoreMap[100] = 0.f;  // 100의 값을 덮어씁니다.

float ReturnValue(std::map<int, float>& InScoreMap, const int InKey)
{
	return InScoreMap[InKey]; 
		// 만약 InKey에 해당하는 키-벨류 쌍이 InScoreMap에 없다면, 그 순간 (InKey, 0.f) 페어를 삽입해버리고 0.f가 반환됩니다.
		// Map 컨테이너의 배열 첨자 연산자를 사용할 땐 용법을 정확히 이해하고 사용하는 것이 중요합니다.
}

 

// Main.cpp

#include <iostream>
#include <map>

void PrintScores(const std::map<int, float>& InScoreMap);

int main()
{
	std::map<int, float> ScoreMap;

	ScoreMap.insert(std::pair<int, float>(100, 95.f));
	ScoreMap.insert(std::pair<int, float>(110, 70.f));

	PrintScores(ScoreMap);

	ScoreMap[110] = 90.f;

	PrintScores(ScoreMap);

	ScoreMap.insert(std::pair<int, float>(100, 90.f));
		// operator[]와 다르게 중복 키가 삽입되어도 값 덮어쓰기가 발생하지 않습니다.

	std::cout << "ScoreMap[100] Score: " << ScoreMap[100] << std::endl;

	std::cout << "ScoreMap[115] Score: " << ScoreMap[115] << std::endl;
		// 이렇게 읽으면 위험할 수 있습니다. 읽기만 하려 했는데,
		// 해당 키가 없다면 키-값 쌍 생성 및 값을 0에 준하는 값으로 초기화 해버립니다.
		// 즉, ReadOrCreate

	return 0;
}

void PrintScores(const std::map<int, float>& InScoreMap)
{
	std::map<int, float>::const_iterator Iter = InScoreMap.begin();
	while (Iter != InScoreMap.end())
	{
		std::cout << "ID: " << Iter->first << " Value: " << Iter->second << std::endl;
		++Iter;
	}

	std::cout << "Size: " << InScoreMap.size() << std::endl << std::endl;
}

 

 

C++ 맵은 key 기준으로 자동정렬된다.

 

// Main.cpp

#include <iostream>
#include <map>

void PrintScores(const std::map<int, float>& InScoreMap);

int main()
{
	std::map<int, float> ScoreMap;

	ScoreMap.insert(std::pair<int, float>(110, 70.f));
	ScoreMap.insert(std::pair<int, float>(200, 98.f));
	ScoreMap.insert(std::pair<int, float>(100, 95.f));

	PrintScores(ScoreMap);

	return 0;
}

void PrintScores(const std::map<int, float>& InScoreMap)
{
	std::map<int, float>::const_iterator Iter = InScoreMap.begin();
	while (Iter != InScoreMap.end())
	{
		std::cout << "ID: " << Iter->first << " Value: " << Iter->second << std::endl;
		++Iter;
	}

	std::cout << "Size: " << InScoreMap.size() << std::endl << std::endl;
}

 

 

map에서 [] 연산자로 요소 찾기

요소가 없는 경우에는 삽입을 하니, 요소 찾기는 불가능하다.

 

  • iterator find() 함수
    • map에서 key를 찾으면 그에 대응하는 값을 가리키는 반복자를 반환
    • 못 찾으면 end()를 반환
  • erese() 함수
    • map의 요소를
std::map<std::string, int>::iterator FoundIter = ScoreMap.find("Park");
ScoreMap.erase(FoundIter);

 

 

함수 예시

// Main.cpp

#include <iostream>
#include <map>

void PrintScores(const std::map<int, float>& InScoreMap);

int main()
{
	std::map<int, float> ScoreMap;

	ScoreMap.insert(std::pair<int, float>(100, 95.f));
	ScoreMap.insert(std::pair<int, float>(110, 70.f));
	ScoreMap.insert(std::pair<int, float>(200, 98.f));

	PrintScores(ScoreMap);

	std::map<int, float>::iterator Iter = ScoreMap.find(110);
	if (Iter != ScoreMap.end())
	{
		ScoreMap.erase(Iter);
	}

	PrintScores(ScoreMap);

	return 0;
}

void PrintScores(const std::map<int, float>& InScoreMap)
{
	std::map<int, float>::const_iterator Iter = InScoreMap.begin();
	while (Iter != InScoreMap.end())
	{
		std::cout << "ID: " << Iter->first << " Value: " << Iter->second << std::endl;
		++Iter;
	}

	std::cout << "Size: " << InScoreMap.size() << std::endl << std::endl;
}

 

 

사용자 정의 클래스를 키로 사용하는 맵

맵 컨테이너는 이진 탐색 트리를 기반으로 만들어져, 삽입/삭제 시 자동 정렬된다.

사용자 정의 클래스를 키로 사용하려면 정렬 관련 함수를 따로 만들어줘야한다.

// Main.cpp

#include <iostream>
#include <map>
#include <string>

class Score
{
public:
	Score(int InID, float InValue)
		: ID(InID)
		, Value(InValue)
	{
	}

public:
	int ID;
	
	float Value;

};

int main()
{
	std::map<Score, std::string> ScoreMap;

	ScoreMap.insert(std::pair<Score, std::string>(Score(100, 95.f), "Kim"));
	ScoreMap.insert(std::pair<Score, std::string>(Score(110, 70.f), "Lee"));
	ScoreMap.insert(std::pair<Score, std::string>(Score(200, 98.f), "Park"));

	return 0;
}

 

 

반드시 두 키를 비교하는 함수를 만들어야한다.

  1. operator<()
  2. comparer
// Main.cpp

#include <iostream>
#include <map>
#include <string>

class Score
{
public:
	Score(int InID, float InValue)
		: ID(InID)
		, Value(InValue)
	{
	}

	// 1. operator<()를 재정의하는 방법.
	bool operator<(const Score& InScore) const
	{
		if (ID == InScore.ID)
		{
			return Value < InScore.Value;
		}

		return ID < InScore.ID;
	}

public:
	int ID;
	
	float Value;

};

// 2. Comparer 구조체를 만들어서 operator()를 재정의하는 방법.
struct ScoreComparer
{
	bool operator()(const Score& InScore1, const Score& InScore2) const
	{
		if (InScore1.ID == InScore2.ID)
		{
			return InScore1.Value > InScore2.Value; // 내림차순
		}
		return InScore1.ID > InScore2.ID;
	}
};

int main()
{
	std::map<Score, std::string> ScoreMap01;
	ScoreMap01.insert(std::pair<Score, std::string>(Score(100, 95.f), "Kim"));
	ScoreMap01.insert(std::pair<Score, std::string>(Score(200, 98.f), "Park"));
	ScoreMap01.insert(std::pair<Score, std::string>(Score(110, 70.f), "Lee"));

	{
		std::map<Score, std::string>::const_iterator Iter = ScoreMap01.begin();
		while (Iter != ScoreMap01.end())
		{
			std::cout << "ID: " << Iter->first.ID << ", Value: " << Iter->first.Value << ", Name: " << Iter->second << std::endl;
			++Iter;
		}

		std::cout << "Size: " << ScoreMap01.size() << std::endl << std::endl;
	}

	std::map<Score, std::string, ScoreComparer> ScoreMap02;
	ScoreMap02.insert(std::pair<Score, std::string>(Score(100, 95.f), "Kim"));
	ScoreMap02.insert(std::pair<Score, std::string>(Score(110, 70.f), "Lee"));
	ScoreMap02.insert(std::pair<Score, std::string>(Score(200, 98.f), "Park"));
	
	{
		std::map<Score, std::string, ScoreComparer>::const_iterator Iter = ScoreMap02.begin();
		while (Iter != ScoreMap02.end())
		{
			std::cout << "ID: " << Iter->first.ID << ", Value: " << Iter->first.Value << ", Name: " << Iter->second << std::endl;
			++Iter;
		}

		std::cout << "Size: " << ScoreMap02.size() << std::endl << std::endl;
	}

	return 0;
}

 

가끔 다른 프로그래머가 만든 클래스를 키로 사용해서 맵을 선언하는 경우도 있으므로 operator<() 함수를 해당 클래스에 마음대로 끼워넣기 애매할 수 있다. 이럴 때 comparer 개념을 활용한다.

 

unordered_map

키와 값의 쌍(pair)들을 저장한다.

키는 중복 저장이 불가능하다는 것까지는 map 컨테이너와 유사.

map은 키의 대소 비교로 탐색하지만 unordered_map은 키를 해시 함수의 인자로 넣어 계산된 해시값을 통해 탐색하는 해시 테이블 기반의 STL이다.

// Main.cpp

#include <iostream>
#include <string>
#include <unordered_map>

int main(void)
{
    std::unordered_map<std::string, int> Scores;
    Scores["Kim"] = 100;
    Scores["Lee"] = 70;
    Scores["Park"] = 99;

    for (auto Iter = Scores.begin(); Iter != Scores.end(); ++Iter)
    {
        std::cout << Iter->first << " : " << Iter->second << std::endl;
    }

    return 0;
}

 

 

unorderd_map 컨테이너의 find() 함수

// Main.cpp

#include <iostream>
#include <string>
#include <unordered_map>

int main(void)
{
	std::unordered_map<std::string, int> Scores;
	Scores["Kim"] = 100;
	Scores["Lee"] = 70;
	Scores["Park"] = 99;

	for (auto Iter = Scores.begin(); Iter != Scores.end(); ++Iter)
	{
		std::cout << Iter->first << " : " << Iter->second << std::endl;
	}

	auto FoundIter = Scores.find("Lee");
	if (FoundIter != Scores.end())
	{
		std::cout << "Lee's score is " << FoundIter->second << std::endl;
	}

	return 0;
}

 

 

가끔 키가 다른데도 같은 해시값이 나올 수 있으며, 이를 해시 충돌이라고 한다.

이럴 경우 하나의 버킷에 둘 이상의 데이터가 들어간다.

/* main.cpp */

#include <iostream>
#include <string>
#include <unordered_map>

int main(void)
{
    std::unordered_map<std::string, int> Scores;
    Scores["Kim"] = 10;
    Scores["Lee"] = 20;
    Scores["Park"] = 30;

    for (size_t i = 0; i < Scores.bucket_count(); ++i)
    {
        std::cout << "Bucket #" << i << ": ";
        for (auto Iter = Scores.begin(i); Iter != Scores.end(i); ++Iter)
        {
            std::cout << ' ' << Iter->first << ':' << Iter->second;
        }
        std::cout << std::endl;
    }

    return 0;
}

 

 

map vs. unordered_map                                     

std::map std::unordered_map
자동으로 정렬되는 컨테이너 자동으로 정렬되지 않는 컨테이너
키-값 쌍들을 저장 키-값 쌍들을 저장
이진 탐색 트리 해시 테이블
탐색 시간이 O(logN) 탐색 시간이 O(1) 해시 충돌시 최악의 경우 O(N)
삽입과 제거가 빈번하면 느림. 버킷 때문에 공간 복잡도 증가.