unreal 5기

250820 언리얼엔진 본캠프 12일차 STL 기초

parkjinnam 2025. 8. 20. 16:00

STL

오늘은 STL에 대해서 배워보았다. STL은 Standard Template Library의 약자로 C++ 표준 라이브러리의 일부이다. 데이터에 의존하지 않고 사용 가능한 라이브러리다. 라이브러리는 크게 세 가지로 이루어져 있는데

 

1. 컨테이너 : 무언가를 담음

2. 알고리즘 : 동작

3. 반복자 : 말 그대로

 

이렇게 세 가지이다 하나하나 알아보도록 하자

 

1. 컨테이너

컨테이너는 데이터를 담는 자료 구조이다. 데이터를 담는 방식이나 제공하는 메서드에 따라 여러 가지 컨테이너를 제공하며 다음과 같은 특징을 갖는다.

 

1) 모든 컨테이너는 탬플릿으로 구현되어 있음, 다양한 타입의 데이터 저장 가능

2) 모든 컨테이너는 메모리 관리를 내부적으로 한다. 즉 메모리 해제를 직접 고려하지 않아도 ok

3) 대부분의 컨테이너는 반복자를 제공한다. 따라서 내부 구현을 몰라도 동일한 형식으로 컨테이너를 순회 가능

 

이러한 컨테이너에는 대표적인 사례가 있는데 그중 하나가 벡터이다. 벡터는 배열과 매우 유사한 컨테이너로서 탬플릿 클래스로 구현되어 특정 타입에 종속되지 않으며 삽입되는 원소 개수에 따라 내부 배열의 크기가 자동으로 조정되며 임의로 접근이 가능하다.

벡터를 다룰 때의 팁으로는 삽입/삭제는 맨 뒤에 하는 것이 좋다고 한다. 그럼 벡터는 어떻게 선언해야 할까? 타입만 명시해서 선언하거나 초기값까지 같이 선언하는 경우(변수 선언하듯), 초기화 리스트를 사용하여 벡터를 선언한다.

 

 

vector <int> vec (초기값 없이 타입만 명시)

 

vector<int> vec2(5, 10) (크기는 5이고 각 원소들의 초기값이 10인 벡터를 타입과 함께 초기화)

 

vector<int> vec3 = {1, 2, 3, 4, 5}; (원소의 개수가 적을 때, 특정 값으로 벡터를 초기화할 때 자주 사용)

 

변수 때처럼 다른 벡터에 벡터를 복사하거나 대입하는 방법도 유효하다.

벡터는 배열과 유사하다고 앞에서 말했다. 그 말마따나 2차원 배열'처럼' 벡터를 사용할 수 있는데 벡터의 타입을 벡터로 하면 된다.

 

 

유의해야 할 건 배열과 같은 게 아니라 배열처럼 사용 가능하다는 것이다.

 

다음으로는 벡터에서 제공하는 동작을 알아보자.

 

1) push back : 벡터의 끝에 값을 추가하는 동작이다.

(ex : vec.push_back(10) == 벡터의 끝에 10을 추가)

 

2) pop_back : 벡터의 맨 끝의 원소를 제거하는 동작 이때 벡터의 크기는 자동으로 줄어든다.

(ex : vec.pop_back())

 

3. size : 벡터의 크기(원소의 개수)를 확인할 때 사용하는 동작 벡터의 전체원소를 대상으로 반복문 사용 시 유용

(ex : vec.size())

 

4. erase : 특정 위치 또는 구간의 원소를 제거하는 함수. 벡터는 내부적으로 배열을 사용하므로 중간 원소를 삭제할 때 많은 원소를 옮겨야 한다. 따라서 시간 복잡도가 커질 수 있기 때문에 자주 사용하지 않는 것이 좋다.

(ex : vec.erase(vec.begin()+1))

 

다음으로 배울 컨테이너 유형은 맵이다 배열은 정수형 인덱스를 활용하여 특정 위치의 값을 빠르게 찾아주지만 맵은 키를 활용하여 값가 쌍으로 저장하고 검색을 한다. 맵의 특성으로는 다음과 같이 있다.

 

1) 키-값 쌍은 pair<const Key, Value> 형태로 저장된다.

2) 키값을 기준으로 내부 데이터가 자동으로 정렬한다.

3) 중복된 키값을 허용하지 않는다.

 

자 맵을 한번 선언해 보도록 하자 맵은 키-값의 쌍을 저장하기 위해서 값 타입을 두 가지를 지정해야 한다.

map<int, string> studentMap; (int : Key타입, string : Value 타입)

 

자 이제 맵을 어떻게 사용하면 좋을까? 일단 맵에 요소들을 추가해 보자

 

studentMap[101] = "Jin" (101번 키에 Jin저장)

studentMap[102] = "Nam" (102번 키에 Nam 저장)

 

이런 식으로 작성하면 선언한 맵에 키와 값을 저장할 수 있고 그 값은 pair를 통해서 불러올 수 있다. 예컨대

 

for(const auto& pair : studentMap)

{

cout << "ID : " << pair.first << ", Name : " << pair.second << endl;

}

 

이런 식으로 코드를 작성하면 for반복문을 통해서 저장된 키들과 그에 맞는 값들이 순차적으로 콘솔에 출력될 것이다. 이때 pair.first가 키 pair.second가 값을 불러오는 역할을 한다. 맵은 key의 오름차순으로 정렬된다. 이는 별도로 사용자가 정렬을 하지 않아도 삽입, 삭제가 이루어질 때마다 내부적으로 정렬상태를 유지하기 때문이다.

앞서 공부한 맵의 다양한 동작을 알아보자. 처음으로는 맵에 키-값을 추가하는 insert 함수이다

 

1) insert()

맵에 키-값 쌍을 추가하는 함수, 다양한 방식으로 응용가능하다 예시를 한번 봐보자

 

studentMap.insert(make_pair(1, "Park"));  // 1번 키와 Park이란 값의 쌍을 만들어서 인설트

 

이렇게 작성해 주면 1번 키에 Park이란 값을 studentMap에 추가할 수 있다. 아니면 똑같이 insert를 사용하되 make_pair를 사용하지 않는 방법도 있다.

 

studentMap.insert({105, Hi});  // 105번 키에 Hi 값 추가

 

번외)

studentMap[106] = "Hello" // 배열과 유사하게 이런 식으로 추가도 가능하다

 

2) find()

특정 키가 맵에 존재하는지 확인할 수 있다. find는 키가 존재하면 해당 키의 이터레이터를 반환하고 존재하지 않으면 map.end()를 반환한다.

 

studentMap.find(key); // key값엔 찾길 원하는 key를 넣어주면 된다

 

3) size()

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

 

4) erase(key)

맵의 특정 키를 가진 요소만 삭제한다

 

5) clear()

맵의 모든 요소를 삭제한다

 

2. 알고리즘

STL은 다양한 컨테이너와 더불어 독립적으로 동작하는 범용 알고리즘을 제공한다. 예컨대 특정 원소 값을 찾거나 정렬을 하는 등의 기능을 표준 라이브러리에서 바로 사용할 수 있다. 더 좋은 점은 컨테이너의 내부 구현을 몰라도 동일한 방식으로 알고리즘을 적용할 수 있다는 점이다. 이러한 기능은 반복자 덕분에 가능하다.

 

1) sort()

컨테이너 내부의 데이터를 정렬하는 함수이다. 기본타입의 경우 사용자 정렬 함수가 없다면 오름차순으로 정렬된다. 또한 사용자 정렬 함수(comp(a, b))를 정의할 수도 있다.

 

sort(arr, arr + size); // int size = sizeof(arr)/sizeof(arr[0]);

 

comp(a, b)를 구현할 때 알아야 할 점

 

a) 현재 컨테이너에서 첫 번째 인자 a가 앞에 있는 원소를 의미한다.

 

b) comp(a, b)가 true이면 a와 b의 순서는 유지된다. 만약 false이면 a와 b의 순서를 바꾼다.

 

bool compare(int a, int b)

{

     return a>b; // 내림차순 정렬

}

 

sort(arr, arr + size, compare);    // 세 번째 인자에 사용자 정렬함수가 들어간다.

 

sort(vec.begin(), vec.end(), compare); // 반복자를 이용한 정렬

 

2) find()

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

 

a) find(first, last)가 탐색 대상  // first <= x < last

b) 원소를 찾은 경우 해당 원소의 반복자를 반환한다.

c) 원소를 찾지 못한 경우 last 반복자를 반환한다.

 

3. 반복자

컨테이너의 내부 구조는 서로 다르지만 대부분 알고리즘을 동일한 코드를 활용해서 사용할 수 있었다. 즉 컨테이너의 구현 방식에 의존하지 않고(내부 구현을 몰라도) 알고리즘을 활용하는데 문제가 없다는 의미이다. 이는 반복자를 기반으로 해서 알고리즘이 동작하기 때문이다. 반복자는 컨테이너의 요소에 대한 일괄된 접근 방법을 제공하므로 알고리즘이 특정 컨테이너의 내부 구현과 무관하게 동작할 수 있다.

 

 1) 순방향 반복자

앞에서부터 뒤로 순차적으로 순회하는 반복자.

 

a) begin() 은 컨테이너의 첫 번째 원소를 가리키는 반복자.

b) end()는 컨테이너의 마지막 우너소를 가리키는 반복자.

 

end()를 만 지막 원소 다음을 가리키도록 정한 이유는 일관된 반복 구조를 유지하기도 하여 탐색 실패를 쉽게 표현할 수 있기 때문이다.

 

2) 역방향 반복자

마지막 원소부터 첫 번째 원소까지 역순으로  순회할 수 있도록 해주는 반복자.

 

a) rbegin()은 컨테이너의 마지막 원소를 가리키는 역방향 반복자.

b) rend()는 컨테이너의 첫 번째 원소를 가리키는 역방향 반복자.

 

순방향 반복자와 사용 방법은 같다.