C++ - 자주 쓰는 STL
C++ 자주 쓰는 STL
1. STL 개요
STL(Standard Template Library)는 프로그래밍 언어를 위해 처음 설계한 소프트웨어 라이브러리이다.
원래라면 C++에만 있는건 아니라는 뜻이다. 하지만 C++ 표준 라이브러리에 많은 영향을 끼쳤으며 이는 컨테이너, 알고리즘, 반복자, 기능으로 구분된다.
1) 컨테이너(Container)
이 컨테이너는 순서 컨테이너와 연관 컨테이너가 있다.
a. 표준 순서 컨테이너
말 그대로 순서가 있는 것들이다. vector, deque, list
b. 표준 연관 컨테이너
순서는 없지만 연관되어있는 것들이다. set, map, hash_set, hash_map
2) 반복자(Iterators)
반복자는 어떤 값을 반복적으로 처리하기 위한 것으로 영어로는 Iterators를 말한다.
이 반복자는 아래와 같이 5 종류가 있다.
- 입력 반복자 (값 시퀀스를 읽는 데만 사용 가능)
- 출력 반복자 (값 시퀀스를 쓰는 데만 사용 가능)
- 순방향 반복자 (읽고, 쓰고, 앞으로 이동할 수 있음)
- 양방향 반복자 (순방향 반복자와 유사하지만 뒤로 이동할 수도 있음)
- 임의 접근 반복자 (한 작업에서 원하는 수의 단계를 자유롭게 이동할 수 있음).
3) 알고리즘(Algorithm)
검색이나 정렬과 같은 작업을 수행 할 수 있는 알고리즘을 말한다.
- sort, binary search
4) 함수 객체(Function Object)
STL에는 함수 호출 연산자를 오버로드하는 클래스가 포함되어있는데 이러한 클래스의 인스턴스를 함수 객체라고 한다. 이 함수 객체는 연관된 함수의 동작을 매개변수화할 수 있도록 하며, 함수와 함께 연관된 함수 객체 정보를 유지하는데 사용될 수 있다.
그냥 들으면 이게 뭔 소린가 싶은데 그냥 해당 STL을 함수 처럼 쓸수 있게 해준다는 뜻이다.
아래의 예시를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
class Add {
public: // 중요! class는 기본 접근이 private이므로 명시적으로 public으로 열어줘야 함
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
Add add;
std::cout << add(3, 5) << std::endl; // 출력: 8
}
위와 같이 class를 선언한다면 객체이지만 함수같이 사용할 수 있으며 이를 함수 객체라고 한다.
2. 자주 쓰는 STL
1) Array
a. 선언
사용하기 위해서는 array 헤더를 추가해야한다.
1
2
3
#include<array>
array<int, 5> arr; // int형으로 크기 5의 array를 선언
b. 값 추가
std::array는 고정 크기이므로 원소를 추가할 수 없지만, 인덱스를 통해 값을 할당한다.
1
2
3
arr[0] = 10;
arr[1] = 20;
arr.at(2) = 30; // 범위 체크가 포함된 접근
c. 업데이트
인덱스를 통해 직접 값을 변경한다.
1
2
3
4
if (!arr.empty()) {
arr[3] = 40; // operator[] 사용
arr.at(4) = 50; // at() 사용
}
※ 성능을 살펴볼때 array보단 vector가 더 낫다는 말도 있다.
d. 삭제
std::array는 고정 크기이므로 원소를 삭제할 수 없다. 대신 모든 원소를 초기화하거나 기본값으로 채운다
1
arr.fill(0); // 모든 원소를 0으로 초기화
2) Vector
a. 선언
사용하기 위해서는 vector 헤더를 추가해야한다.
1
2
3
4
5
#include<vector>
vector<int> vec; // int형 가변크기 vector 선언
vector<int> vec2 = {1,2,3}; // int형 가변크기 vector에 1,2,3으로 초기화
vector<int> vec3(vec2); // vec2와 동일한 값으로 초기화
Vector는 가변 크기지만 미리 크기를 할당해둘수도 있다.
1
2
3
4
5
6
7
8
vector<int> vec1(4) // 크기 4로 생성
vector<int> vec2(10,4); // 크기 10에 4로 초기화
vector<vector<int>> vec3(9,vector<int>(8,6) // 크기 8이고 6으로 초기화된 벡터들이 총 9개 (8x9, 2차원 벡터)
vector<vector<int>> vec4 = {
{1,2,3},
{4,5,6},
{7,8,9}
} // 초기화 리스트를 이용하여 2차원 벡터 초기화
b. 값 추가
값을 뒤에서 넣기와 지정해서 해당 위치에 넣기가 있으며, 지정해서 해당 위치에 넣기보다 뒤에서 넣기가 성능이 더 좋다.
1
2
vec.push_back(10); // 맨 뒤에 값 추가
vec.insert(vec.begin(), 5); // 지정한 위치에 값 추가
c. 업데이트
1
2
3
4
if (!vec.empty()) {
vec[0] = 15; // operator[] 사용
vec.at(1) = 20; // at() 사용
}
d. 삭제
1
2
3
4
5
6
7
8
9
10
11
// 특정 위치의 원소 삭제
if (!vec.empty()) vec.erase(vec.begin() + 1);
// 제일 뒤의 원소 삭제
vec.pop_back();
// 값이 10인 모든 원소 삭제 (erase-remove idiom)
vec.erase(std::remove(vec.begin(), vec.end(), 10), vec.end());
// 전체 삭제
vec.clear();
3) Set
a. 선언
사용하기 위해서는 set 헤더를 추가해야한다.
1
2
3
4
5
#include<set>
set<int> intSet; // int형으로 set 선언
set<int> intSet2 = {1,2,3}; // set을 1,2,3으로 초기화
set<int> intSet3(intSet2); // set을 intSet2로 생성
b. 값 추가
1
2
intSet.insert(10);
intSet.insert(20);
c. 값 탐색
1
2
3
4
5
6
7
8
9
10
set<int> datas = {1,2,3,4,5};
auto targetIndex = datas.find(3);
if(targetIndx != datas.end()){
// 해당 값이 set에 있음
}
else{
// 해당 값이 set에 없음
}
※ 사실 set을 쓸 때 뭔가를 넣어두고 안에 값이 있는지 없는지 체크하려고 쓰는건데, 위와 같이 쓰면 매우 번거롭다. 그래서 나는 개인적으로 별도의 has 함수를 만들어서 사용한다.
1
2
3
4
5
6
#include <set>
template <typename T>
bool has(const std::set<T>& s, const T& value) {
return s.find(value) != s.end();
}
아니면 별도의 wrapper class를 만들어서 사용할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <set>
template <typename T>
class SetWithHas {
private:
std::set<T> internalSet;
public:
// 기본 생성자
SetWithHas() = default;
// insert 위임
void insert(const T& value) {
internalSet.insert(value);
}
// erase 위임
void erase(const T& value) {
internalSet.erase(value);
}
// 해당 값이 있는지 체크
bool has(const T& value) const {
return internalSet.find(value) != internalSet.end();
}
// 반복자 위임
typename std::set<T>::const_iterator begin() const {
return internalSet.begin();
}
typename std::set<T>::const_iterator end() const {
return internalSet.end();
}
// 갯수 반환
std::size_t size() const {
return internalSet.size();
}
// 필요하다면 추가 함수 가능
};
하지만 SET을 상속받거나 SET을 직접 고치는건 권장되지 않는다.
c++20 부터는 아예 contain이라는 함수를 지원한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> testSet{1, 2};
for (int x : {2, 3})
if (testSet.contains(x))
cout << x << ": is here\n";
else
cout << x << ": is not here\n";
}
d. 업데이트
Set은 키 기반으로 정렬된 컨테이너이므로 직접 수정할 수 없다. 값을 변경하려면 기존 값을 지우고 새 값을 삽입한다.
1
2
3
4
5
6
7
int oldValue = 10;
int newValue = 15;
auto pos = intSet.find(oldValue);
if (pos != intSet.end()) {
intSet.erase(pos);
intSet.insert(newValue);
}
e. 삭제
1
2
3
intSet.erase(20); // 값이 20인 요소 삭제
intSet.clear(); // 모든 요소 삭제
4) Map
a. 선언
사용하기 위해서는 map 헤더를 추가해야한다.
1
2
3
4
#include<map>
map<int, int> intMap; // int형을 키와 값으로 갖는 map을 선언
map<int, int> intMap2 = {{1,3}}; // int형키 1과 int형 value 3을 갖는 map을 선언
b. 값 추가
1
2
3
4
// insert 사용
intMap.insert(std::make_pair(1, 100));
// 또는 operator[] 사용
intMap[2] = 200;
c. 값 탐색
1
2
3
4
5
6
7
8
9
10
11
#include <map>
map<int,int> datas = {{1,2},{2,3},{3,4},{4,5},{5,6}};
auto targetIndex = datas.find(3);
if(targetIndx != datas.end()){
// 해당 값이 map에 있음
}
else{
// 해당 값이 map에 없음
}
위의 set 함수에서와 마찬가지로 has 함수를 따로 만들어 쓸수도있다.
1
2
3
4
5
6
#include <map>
template <typename T1, typename T2>
bool has(const std::map<T1,T2>& m, const T1& key) {
return m.find(key) != m.end();
}
아니면 별도의 wrapper class를 만들어서 사용할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <map>
template <typename T1, typename T2>
class MapWithHas {
private:
std::map<T1,T2> internalMap;
public:
// 기본 생성자
MapWithHas() = default;
// insert 위임
void insert(const T1& key,const T2& value) {
internalSet[key] = value;
}
// erase 위임
void erase(const T1& key) {
internalSet.erase(key);
}
// 해당 값이 있는지 체크
bool has(const T1& key) const {
return internalMap.find(key) != internalMap.end();
}
// 반복자 위임
typename std::map<T1,T2>::const_iterator begin() const {
return internalMap.begin();
}
typename std::map<T1,T2>::const_iterator end() const {
return internalMap.end();
}
// 갯수 반환
std::size_t size() const {
return internalMap.size();
}
// 필요하다면 추가 함수 가능
};
하지만 MAP을 상속받거나 MAP을 직접 고치는건 권장되지 않는다.
c++20 부터는 아예 contain이라는 함수를 지원한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<int> testMap{1, 2};
for (int x : {2, 3})
if (testMap.contains(x))
cout << x << ": is here\n";
else
cout << x << ": is not here\n";
}
d. 업데이트
1
intMap[2] = 250; // operator[]로 key가 2인 값 변경
e. 삭제
1
2
3
intMap.erase(1); // 키 1을 삭제
intMap.clear(); // 전체 삭제
참고자료
- 위키독스 - C++ 이야기
- c++20 공식문서
- 위키백과 - Standard Template Library
- 코딩테스트 합격자 되기 C++ 편. 자료구조, 알고리즘, 빈출 100문제로 대비하는 코테 풀패키지, 박경록, (주)골든 래빗
- cppreference - std::set<key,compare,allocator>::contains
- cppreference - std::map<Key,T,Compare,Allocator>::contains