본문 바로가기

[게임 개발자를 위한 C++ 문법] STL 기초

@iamrain2025. 8. 21. 11:35

`STL`(Standard Template Library)은 C++ 표준 라이브러리의 일부로 컨테이너, 알고리즘, 반복자 등의 템플릿 기반 구성 요소를 포함한다.

 

컨테이너

데이터를 담는 자료구조를 컨테이너라고 한다.

데이터를 담는 방식이나 제공하는 메서드에 따라 여러 컨테이너를 제공한다.

 

모든 컨테이너는 템플릿으로 구현되어 있으므로, 다양한 타입의 데이터를 저장할 수 있다.

내부적으로 메모리 관리를 하기 때문에 사용 시 메모리 해제를 직접 고려하지 않아도 된다.

대부분의 컨테이너는 반복자를 제공해서 내부 구현을 몰라도 동일한 방식으로 컨테이너를 순회할 수 있다.

 

벡터

배열과 매우 유사한 컨테이너로 다음과 같은 특징을 가지고 있다.

  • 템플릿 클래스로 구현되어 특정 타입에 종속되지 않는다.
  • 삽입되는 원소 개수에 따라 내부 배열의 크기가 자동으로 조정된다.
  • 임의 접근(인덱스를 통한 특정 위치 접근)이 가능하다.
  • 삽입 / 삭제는 맨 뒤에 하는게 좋다. (중간 삽입 / 삭제는 배열 복사가 필요하므로 비효율적)

벡터의 선언

타입만 명시해서 선언하는 방법과 초기값까지 같이 선언하는 방법이 있다.

 

빈 벡터를 선언하거나 특정 값으로 초기화 하는 코드

#include <vector>
using namespace std;

vector<int> vec1;

vector<int> vec2(5, 10);

초기화 리스트를 사용해 벡터를 선언하는 코드

#include <vector>
using namespace std;

vector<int> vec3 = {1, 2, 3, 4, 5};

다른 벡터를 복사하거나 대입하는 코드

#include <vector>
using namespace std;

vector<int> vec3 = {1, 2, 3, 4, 5};
vector<int> vec4(vec3);
2차원 배열처럼 벡터를 사용하려면, 아래와 같이 벡터의 타입을 벡터로 하면 된다.

`vector<vector<int>> vec2D(3, vector<int>(4, 7));`

벡터의 동작

`push_back`

벡터의 맨 끝 원소를 추가하는 메서드다.
원소의 개수가 늘어남에 따라 크기가 자동으로 증가하므로, 별도의 메모리 관리를 신경 쓸 필요없다.

`pop_back`

벡터의 맨 끝 원소를 제거하는 메서드다.
맨 끝 원소가 제거되면 벡터 크기가 자동으로 줄어든다.

`size`

현재 벡터의 크기(원소 개수)를 확인할 때 사용하는 메서드다.

보통 벡터의 전체 원소를 대상으로 반복문을 돌릴 때 유용하게 쓰인다.

`erase`

특정 위치(또는 구간)의 원소를 제거하는 함수다.

벡터는 내부적으로 배열을 사용하는데 중간 원소를 삭제할 때 많은 원소를 옮겨야 해서 시간 복잡도가 커질 수 있으므로 자주 사용하지 않는 것이 좋다.

 

맵은 특정 키를 사용해 값을 검색하는 기능을 제공하는 컨테이너다.

 

배열은 정수형 인덱스를 활용해 특정 위치의 값을 빠르게 찾아주지만, 맵은 키를 활용해 값과 쌍으로 저장하고 검색한다.

주요 특성은 아래와 같다.

  • 키-값 쌍은 `pair<const Key, Value>`형태로 저장된다.
  • 키값을 기준으로 내부 데이터가 자동으로 정렬된다.
  • 중복된 키값이 허용되지 않는다.

맵 선언

`map`은 `key` 순으로 오름차순 정렬이다.

별도로 정렬을 수행하지 않아도, 삽입 및 삭제가 이루어질 때 내부적으로 정렬 상태를 유지한다.

 

`insert()`

`make_pair()`를 이용해 `pair` 객체를 생성한 후 `insert` 함수를 사용할 수 있다. `{ }`나 `[ ]`를 사용해 값을 추가할 수 있다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap;

    myMap.insert(make_pair(1, "Alpha"));
    myMap.insert({2, "Bravo"});
    myMap[3] = "Charlie";

    for (const auto& pair : myMap) {
        cout << "Key: " << pair.first << ", Value: " << pair.second << endl;
    }

    return 0;
}

`find()`

`find` 메서드를 사용해 특정 키가 `map`에 존재하는지 확인할 수 있다.

`find`는 키가 존재하면 해당 키의 `iterator`를 반환하고, 존재하지 않으면 `map.end()`를 반환한다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap = {
        {1, "Alpha"},
        {2, "Bravo"},
        {3, "Charlie"}
    };

    int key = 2;
    auto it = myMap.find(key);

    if (it != myMap.end()) {
        cout << "Found! Key: " << it->first << ", Value: " << it->second << endl;
    } else {
        cout << "Key " << key << " not found!" << endl;
    }

    return 0;
}

`size()`

맵의 키-값 쌍의 개수를 반환하는 함수다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap;

    myMap[1] = "Alpha";
    myMap[2] = "Bravo";
    myMap[3] = "Charlie";

    cout << "Map size: " << myMap.size() << endl;

    return 0;
}

`erase(key)`

맵의 특정 `key`를 가진 요소만 삭제한다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap = {
        {1, "Alpha"},
        {2, "Bravo"},
        {3, "Charlie"}
    };

    cout << "Before erase, size: " << myMap.size() << endl;

    myMap.erase(2);

    cout << "After erase(2), size: " << myMap.size() << endl;

    for (const auto& pair : myMap) {
        cout << "Key: " << pair.first << ", Value: " << pair.second << endl;
    }
    
    // 존재하지 않는 키 삭제 시도
    int removed = myMap.erase(40);

    if (removed == 0) {
        cout << "Key 40 not found. No deletion performed." << endl;
    }

    return 0;
}

`clear`

맵에 있는 모든 원소를 삭제하는 함수로 대부분의 컨테이너에 존재한다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap = {
        {1, "Alpha"},
        {2, "Bravo"},
        {3, "Charlie"}
    };

    cout << "Before clear, size: " << myMap.size() << endl;

    myMap.clear();

    cout << "After clear, size: " << myMap.size() << endl;

    return 0;
}

 

알고리즘

`STL`은 다양한 컨테이너와 독립적으로 동작하는 범용 알고리즘을 제공한다.

특정 컨테이너의 내부 구현을 몰라도 동일한 방식으로 알고리즘을 적용할 수 있는데, 바로 반복자 덕분이다.

sort

컨테이너 내부의 데이터를 정렬하는 함수로 기본 타입(`int`, `double` 등)의 경우 사용자 정렬 함수가 없으면 오름차순으로 정렬된다.

 

사용자 정렬 함수 `comp(a, b)` 구현 시 2가지를 기억해야 한다.

  • 현재 컨테이너에서 첫 번째 인자 `a`가 앞에 있는 원소를 의미한다.
  • `comp(a, b)`가 `true`이면 a와 b의 순서가 유지된다. `false`이면 순서가 바뀐다.

find

`find`는 컨테이너 내부에서 특정 원소를 찾아 해당 원소의 반복자를 반환하는 함수다.

  • `find(first, last)`가 탐색 대상이다.
  • 원소를 찾은 경우 해당 원소의 반복자를 반환한다.
  • 찾지 못한 경우 `last` 반복자를 반환한다.

 

반복자

컨테이너의 내부 구현을 몰라도 알고리즘을 활용하는데 문제가 없다.

이는 반복자가 컨테이너의 요소에 대한 일관된 접근 방법을 제공해 알고리즘이 동작하기 때문이다.

순방향 반복자

앞에서부터 뒤로 순차적으로 순회한다.

  • `begin()`
    컨테이너의 첫 번째 원소를 가리킴
  • `end()`
    컨테이너의 마지막 원소 다음을 가리킴
`end()`를 마지막 원소 다음을 가리키도록 정한 이유
1. 일관된 반복구조를 유지
2. 탐색 실패를 쉽게 표현

역방향 반복자

컨테이너의 마지막 원소부터 첫 번째 원소까지 역순으로 순회한다.

  • `rbegin()`
    컨테이너의 마지막 원소를 가리킴
  • `rend()`
    컨테이너의 첫 번째 원소 이전을 가리킴
iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차