[C++ Primer Plus] Chapter 16. (3) STL - 컨테이너
Table of Contents
C++ 기초 플러스 책을 읽고 공부한 노트입니다.
컨테이너(container) #
-
컨테이너 개념
- 모든 STL 컨테이너 클래스들이 충족시켜야 하는 요구 사항의 집합
-
기본 컨테이너의 특성
X
vector<int>
T
int
a
,b
vector<int>
의 값
r
vector<int>&
의 값
u
vector<int>
의 객체vec
- 비례 시간
- 원소 수에 비례한다는 것을 의미한다.
표현 | 리턴형 | 설명 | 복잡성 |
---|---|---|---|
X::iterator |
T 를 지시하는 이러레이터형 |
출력 이터레이터를 제외한 모든 이터레이터들 | 컴파일 시간 |
X::value_type |
T |
T 에 해당하는 데이터형 |
컴파일 시간 |
X u; |
크기가 0인 u 라는 컨테이너 생성 |
고정 시간 | |
X(); |
크기가 0인 익명의 컨테이너 생성 | 고정 시간 | |
X u(a); |
복사 생성자 수행이 후 u == a |
비례 시간 | |
X u = a; |
X u(a); 와 같다 |
비례 시간 | |
r = a; |
X& |
복사 대입 수행 이후, r == a |
비례 시간 |
(&a)->~X(); |
void |
컨테이너의 모든 원소에 파괴자를 적용한다. | 비례 시간 |
a.begin() |
이터레이터 | 컨테이너의 첫 번째 원소를 지시하는 이터레이터를 리턴한다. | 고정 시간 |
a.end() |
이터레이터 | past-the-end값인 이터레이터를 리턴한다. | 고정 시간 |
a.size() |
unsigned 정수형 |
a.end() - a.begin() 과 같은 원소 수 |
고정 시간 |
a.swap(b) |
a 와 b 의 내용을 맞바꾼다. |
고정 시간 | |
a == b |
bool 로 변환할 수 있는 데이터형 |
a 와 b 의 크기가 같고, a 에 있는 각 원소가 b 에 있는 대응하는 원소와 등가이면( == 가 true 이면) true |
비례 시간 |
a != b |
bool 로 변환할 수 있는 데이터형 |
!(a -- b) 와 같다. |
비례 시간 |
- C++11에서 추가 요구 사항
rv
- 상수가 아닌
vector<int>&
의 값
- 상수가 아닌
표현 | 리턴형 | 설명 | 복잡성 |
---|---|---|---|
X u(rv); |
생성자 포스트 조건을 이동시킨다. u 는 생성전에 rv 가 지녔던 값을 지닌다. |
선형 | |
X u = rv; |
X u(rv) 와 동일한 효과 |
선형 | |
a = rv; |
X& |
대입 포스트 조건을 이동시킨다. a 는 대입 이전에 rv 가 지녔던 값을 지닌다. |
선형 |
a.cbegin() |
const_iterator |
컨테이너의 첫 번째 요소를 지칭하는 const 이터레이터를 리턴한다. |
상수 |
a.cend() |
const_iterator |
최종값인 const 이터레이터를 리턴한다. |
상수 |
- 컨테이너의 종류
- 1. 시퀀스 컨테이너
- 데이터를 선형으로 저장한다.
- 2. 결합 컨테이너
- 데이터를 키와 값으로 저장한다.
- 3. 컨테이너 어댑터
- 앞의 두 컨테이너를 변형하여 인터페이스를 제한한다.
- 이터레이션을 허용하지 않는다.
- 1. 시퀀스 컨테이너
시퀀스 컨테이너(sequence container) #
- STL의 컨테이너 형 중에서 다음은 컨테이너들은 시퀀스이다.
vector
,deque
,list
,forward_list
(C++11),queue
,priority_queue
,stack
- 시퀀스는 최소한의 이터레이터가 늘 전방 이터레이터이다. 따라서 이터레이션이 이루어질 때마다 변하지 않는 명확한 순서로 원소들이 배치되는 것을 보장한다.
- 또한, 원소들이 직선 순서로 배치된다.
- 시퀀스 요구 사항
t
T
형의 값.int
의 값.
n
- 정수
p
,q
,i
,j
- 이터레이터
표현 | 리턴형 | 설명 |
---|---|---|
X a(n, t) |
n 개의 값 t 로 이루어진 시퀀스 a 를 선언한다. |
|
X(n, t) |
n 개의 값 t 로 이루어진 익명 시퀀스를 선언한다. |
|
X a(i, j) |
[i, j) 범위의 내용으로 초기화된 시퀀스 a 를 선언한다. |
|
X(i, j) |
[i, j) 범위의 내용으로 초기화된 익명 시퀀스를 선언한다. |
|
a.insert(p, t) |
이터레이터 | p 앞에 t 의 복사본을 삽입한다. |
a.insert(p, n, t) |
void |
p 앞에 t 의 복사본을 n 개 삽입한다. |
a.insert(p, i, j) |
void |
p 앞에 [i, j) 범위에 있는 원소들의 복사본을 삽입한다. |
a.erase(p) |
이터레이터 | p 가 지시하는 원소를 삭제한다. |
a.erase(p, q) |
이터레이터 | [p, q) 범위에 있는 원소들을 삭제한다. |
a.clear() |
void |
erase(begin(), end()) 와 같다. |
- 선택적 시퀀스 요구 사항
- 모두 고정 시간 복잡도를 가진다.
표현 | 리턴형 | 의미 | 컨테이너 |
---|---|---|---|
a.front() |
T& |
*a.begin() |
vector , list , deque |
a.back() |
T& |
*--a.end() |
vector , list , deque |
a.push_front(t) |
void |
a.insert(a.begin(), t) |
list , deque |
a.push_back(t) |
void |
a.insert(a.end(), t) |
vector , list , deque |
a.pop_front(t) |
void |
a.erase(a.begin()) |
list , deque |
a.pop_back(t) |
void |
a.erase(--a.end()) |
vector , list , deque |
a[n] |
T& |
*(a.begin() + n) |
vector , deque |
a.at(n) |
T& |
*(a.begin() + n) ( n 이 경계를 벗어나면, out_of_range예외 발생) |
vector , deque |
vector
컨테이너- 이것은 시퀀스이기도 하지만 가역성 컨테이너(reversible container)이기도 하다. 따라서
rbegin()
,rend()
두 개의 메서드가 더 추가된다. - 말미에서는 고정 시간 연산으로 삽입과 삭제가 가능하지만, 다른 위치에서는 비례 시간 연산으로 가능하다.
- 임의 접근을 통한 빠른 접근을 강조한다.
- 이것은 시퀀스이기도 하지만 가역성 컨테이너(reversible container)이기도 하다. 따라서
deque
컨테이너- double-ended queue의 줄임말이며, 양쪽에 끝이 있는 큐이다. 덱이라고 발음한다.
- 양쪽 끝에서 고정 시간 연산으로 삽입과 삭제를 할 수 있도록 한다.
list
컨테이너- 이중 링크드 리스트이다.
- 어느 위치든지 고정 시간 연산으로 삽입과 삭제가 가능하다. 따라서 신속한 삽입과 삭제를 강조한다.
- 이 또한 가역성 컨테이너(reversible container)이다. 하지만
vector
와는 다르게 배열 표기와 임의 접근을 지원하지 않는다.
list<int> fives(3, 5); // {5, 5, 5}
int stuff[7] = {1, 2, 3, 4, 5, 5, 3};
list<int> result;
result.insert(result.begin(), stuff, stuff + 7); // stuff의 0~6를 result에 삽입
for_each(result.begin(), result.end(), output);
cout << endl;
result.remove(5); // {1, 2, 3, 4, 3} 모든 5를 없앤다.
result.splice(result.begin(), fives); // {5, 5, 5, 1, 2, 3, 4, 3}
// fives의 모든 내용이 result.begin() 앞에 삽입된다.
// 결과로, fives는 비게 된다.
result.unique(); // {5, 1, 2, 3, 4, 3} 연속된 중복을 제거한다.
result.sort(); // {1, 2, 3, 3, 4 5} 정렬한다.
sort(result.begin(), result.end()); // (X)
// 멤버가 아닌 sort()도 있다.
// 하지만 이것은 임의 접근 이터레이터를 요구한다. 따라서 리스트와 사용할 수 없다.
list<int> fours(3, 4); // {4, 4, 4}
result.merge(fours); // {1, 2, 3, 4, 4, 4, 4, 5} 정렬되어 있는 두 리스트를 합친다.
forward_list
컨테이너 (C++11)- 단순 링크드 리스트이다.
- 전방 이터레이터만 필요하다. 양방향은 필요하지 않다.
queue
컨테이너- 어댑터 클래스.
- 임의 접근이 안 되며, 큐를 훑는 이터레이션을 허용하지 않는다.
priority_queue
컨테이너- 어댑터 클래스.
- 큐와 같지만, 가장 큰 항목이 큐의 선두로 나간다.
- 내부적으로 다른 점은, 기초가 되는 디폴트 클레스가
vector
이라는 점이다. - 선택적 매개변수를 제공한다. 이것으로 어떤 것을 선두로 내보낼지 결정할 수 있다.
priority_queue<int> pq1;
priority_queue<int> pq2(greater<int>); // greater<int>를 우선 배치한다.
stack
컨테이너- 어댑터 클래스.
- 기초가 되는 디폴트 클래스가
vector
이다. - 임의 접근이 안 되며, 스택을 훑는 이터레이션을 허용하지 않는다.
array
템플릿 클래스- 고정된 크기를 지니기 때문에 STL 컨테이너는 될 수 없다.
copy()
,for_each()
와 같은 표준 STL 알고리즘을 사용할 수 있다.
결합 컨테이너(associative container) #
set
,multiset
,map
,multimap
- 컨테이너 개념의 또 다른 개량이다.
- 값에 키(key)를 결합하고, 그 키를 사용해서 값을 찾는다.
X::key_type
- 키로 사용되는 데이터형
- 특정 위치에 삽입할 수 없다. 왜냐하면, 정보를 신속하게 검색할 수 있도록 데이터의 배치를 결정하는 특별한 알고리즘을 사용하기 때문이다.
- 전형적으로 트리 구조를 이용하여 구현된다. 따라서
list
와 비교했을 때, 검색이 월등히 빠르다.
set
컨테이너- 키와 값의 데이터형이 같다.
- 키당 하나의 값만 가지므로 키는 고유하다.
- 즉, 키가 곧 값이다.
- 가역성이며 정렬된다.
- 키를 배치하는 데 사용되는 비교함수나 객체를 추가적인 매개변수로 제공할 수 있다.
- 디폴트는
less<>
템플릿이 사용된다.
void output(const string& s)
{
cout << s << " ";
}
//...
set<string> s1;
et<string, less<string>> s2;
string stra[5] = {"B", "D", "A", "C", "C" };
set<string> a(stra, stra + 5); // strs 0~4범위로 a를 초기화한다.
// 정렬된다. 그리고 키와 값은 고유하다.
// 따라서 결과는 {"A", "B", "C", "D"} 이다.
string strb[5] = {"E", "G", "A", "B", "F" };
set<string> b(strb, strb + 5);
set<string> uni;
set_union(a.begin(), a.end(), b.begin(), b.end(),
insert_iterator<set<string>>(uni, uni.begin()));
// a와 b의 합집함을 uni에 저장한다.
// insert_iterator가 이것을 가능하게 한다.
// c.begin()만 넣으면, 안된다.
// 이유1. 키는 상수이다. 따라서 c.begin()은 상수 이터레이터이다.
// 그래서 출력 이터레이터로 사용할 수 없다. (쓸 수 없다.)
// 이유2. c는 비어 있다. 데이터를 넣을 충분한 공간이 없다.
// {"A", "B", "C", "D", "E", "F", "G" };
set<string> inter;
set_intersection(a.begin(), a.end(),b.begin(), b.end(),
insert_iterator<set<string>>(inter, inter.begin()));
// a와 b의 교집합
// {"A", "B"}
set<string> dif;
set_difference(a.begin(), a.end(),b.begin(), b.end(),
insert_iterator<set<string>>(dif, dif.begin()));
// a와 b의 차집합
// {"C", "D"}
string stuff = "Apple";
a.insert(stuff); // {"A", "Apple", "B", "C", "D"} 원소를 삽입한다.
for_each(a.lower_bound("Apple"), a.upper_bound("C"), output);
// {"Apple", "B", "C"} 출력
// lower_bound는 매개변수 이상의 것 중 가장 작은 멤버를 지시하는 이터레이터를 리턴한다.
// upper_bound는 매개변수 초과의 것 중 가장 큰 멤버를 지시하는 이터레이터를 리턴한다.
multiset
컨테이너set
과 비슷하다.- 다른 점은, 하나의 키가 다양한 값과 결합될 수 있다.
map
컨테이너- 키와 값의 데이터형이 다르다.
- 키당 하나의 값만 가지므로 키는 고유하다.
multimap
컨테이너map
과 비슷하다.- 다른 점은, 하나의 키가 다양한 값과 결합될 수 있다.
- 가역성이며 정렬된다.
- 키를 배치하는 데 사용되는 비교함수나 객체를 추가적인 매개변수로 제공할 수 있다.
- 디폴트는
less<>
템플릿이 사용된다.
- 키와 값을 하나의 쌍으로 결합하기 위해서
pair<class T, class U>
템플릿 클래스를 사용한다.- `pair<class keytype, class datatype>이 되겠다.
multimap<int, string> codes;
codes.insert({415, "샌프란시스코"});
codes.insert({510, "오클랜드"});
codes.insert({718, "브루클린"});
codes.insert({718, "스태튼 섬"});
cout << codes.count(718) << endl; // count는 키가 718인 값의 개수를 출력한다: 2
multimap<int, string>::iterator it;
for (it = codes.begin(); it != codes.end(); ++it)
cout << (*it).first << " " << (*it).second << endl;
// first, second로 키, 값에 접근할 수 있다.
typedef multimap<int, string>::iterator mapIter;
pair<mapIter, mapIter> range = codes.equal_range(718);
// equal_range은 키가 718인 범위를 나타내는 이터레이터 쌍을 리턴한다.
for (it = range.first; it != range.second; ++it)
cout << (*it).first << " " << (*it).second << endl;
// range에 있는 데이터를 출력한다.
순서가 부여되지 않은 결합 컨테이너(unordered associative container) (C++11) #
- 결합 컨테이너처럼 키와 값을 결합하고 키를 사용해 값을 찾는다.
- 다른 점은, 결합 컨테이너는 트리 구조에 기반을 두지만, 이것은 hash table에 기반을 둔다.
unordered_set
,unordered_multiset
,unordered_map
,unordered_multimap