Skip to main content

[C++ Primer Plus] Chapter 16. (3) STL - 컨테이너

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) ab의 내용을 맞바꾼다. 고정 시간
a == b bool
변환할 수 있는
데이터형
ab의 크기가 같고,
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. 컨테이너 어댑터
      • 앞의 두 컨테이너를 변형하여 인터페이스를 제한한다.
      • 이터레이션을 허용하지 않는다.



시퀀스 컨테이너(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() 두 개의 메서드가 더 추가된다.
    • 말미에서는 고정 시간 연산으로 삽입과 삭제가 가능하지만, 다른 위치에서는 비례 시간 연산으로 가능하다.
    • 임의 접근을 통한 빠른 접근을 강조한다.

  • 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