SlideShare a Scribd company logo
--------------------------------------------------------------------------------
STL (Standard Templete Library) -form www.devpia.com VC+
+ lecture
번역 : Yonghan, Yoon (yonghany@orgio.net)
편집 : Insik, You (kaerun@hanmail.net)  주석(K: )
--------------------------------------------------------------------------------
THE WAITE GROUP"s
Object-Oriented Programming in C+
+ Third Edition
Robert Lafore(Author) SAMS(Publisher)
란 책에서 STL부분만 번역한 겁니다.
--------------------------------------------------------------------------------
0. 개요
STL에서 중요한 세가지 엔티티는 containers, algorithms, iteraters이다.
containers:
메모리에 구조화된 데이터가 저장되는 방법을 말한다. template class로
구현되어 있기 때문에 다른 타입의 데이터를 저장할 수 있게 쉽게 커스터마이징 할 수있다.
algorithms:
-containers가 가진 데이터를 다양한 방법으로 처리하는 프로시져다.
(예, sort, copy, search,merge)
- template class로 구현됨(하지만 container의멤버는 아님)
iterators:
- 포인터 개념을 일반화 시킨것.
- 콘테이너의 데이터를 가리킨다.
- 포인터를 증가시키듯이 iterator를 증가시킬 수 있다. 따라서 iterator는
콘테이너의 다음 데이터를 가리키게 된다.
- iterator는 콘테이너를 가지고 알고리즘을 연결하기 때문에 STL에서 핵심 부분이다.
1.콘테이너
콘테이너는 클래스 오브젝트나 int, float같은 built-in 데이터들을 저장하는 방법을
말한다. STL은 7가지 basic type이 사용가능할 뿐만 아니라, basic type에서 상속된
3가지 타입이 더 있다. 그리고 basic type에서 상속 받는 자신만의 콘테이너를 만들수도 있다.
모든 데이터를 저장할때 왜 C+
+ 배열을 사용하지 않는 거쥐? 이쯤에서 왜 그렇게 많은
콘테이너가 필요한지 궁금할 것이다.
배열은 다루기 힘들고 어떤 상황에선 느리다.
STL에서 콘테이너는 sequence, associative의 2가지 메인 카테고리로 요약된다.
sequence 콘테이너는 vector,list,deque로 분류되고
associative 콘테이너는 set, multiset,map, multimap으로 분류된다.
여기에 추가로 sequence 콘테이너를 상속받은 몇개의 특수한 콘테이너들이 있다.
(stack, queue,priority_queue)
그럼 다음에 차례로 이 카테고리들에 대해서 살펴보기로 하자.
1.1 sequence containers
sequence 콘테이너는 거리에 늘어선 집들처럼 line으로 시각화 할수 있는 요소들의
집합을 저장한다. 각 요소들은 선을 따라서 다른 요소들과 연결되어 있다.
끝요소를 제외한 각 요소들은 어떤 요소들의 앞에 있을 수 있고 뒤에 있을 수도 있다.
일반적인 C+
+ 배열이 sequence 콘테이너의 예라 할 수 있다.
C+
+ 배열의 한가지 문제점은 compile-time에 반드시 크기를 결정해야 한다는 것이다.
그러나 일반적으로 배열에 얼마나 많은 데이터가 저장될지는 알 수 없는 일이다.
그래서 데이터의 최대갯수를 추측하여 충분히 크게 설정하게 된다. 결과적으로
프로그램이 실행되었을 때 사용되지 않는 배열 공간이 낭비되고 또는 실행 공간 부족
에러를 도출해 낸다(심지어는 프로그램이 멈춰버리기 까지 한다). vector 콘테이너는
이러한 복잡성을 피할 수 있는 길을 제공한다.
배열의 또다른 문제점은 employee 레코드를 저장하고 이를 employee이 이름으로 정렬
하라고 한다면 이름이 L로 시작하는 employee를 삽입하려 할때 M부터 Z까지 모든
employee 레코드를 이동시켜야만 한다. 이 작업은 많은 시간이 소요될 것이다.
STL은 이 문제를 풀기 위해 링크드 리스트 개념에 기초한 list 콘테이너를 제공한다.
세번째 sequence container는 stack과 queue를 조합한 것이라 생각할 수 있는 deque 이다.
stack은 last-in-first-out 개념이고, queue는 first-in-first-out개념이다.
따라서 deque는 양끝에서 데이터를 추가 삭제를 할 수 있다. deque라는 단어는
Double-Ended-QUEeue를 조합한 것이다. 융통성 있는 메카니즘으로써 deque자체로도
유용할뿐만 아니라 stack과 queue로도 사용할 수 있다.
테이블 1.1 Basic sequence Container
================================================================================
특징 장단점
================================================================================
일반 C+
+ 배열 고정크기 인덱스 번호를 통한 빠른 액세스
중간값의 추가 삭제 느림.
run-time시에 크기를 변경할 수 없다.
--------------------------------------------------------------------------------
vector 재배치,확장배열 인덱스 번호를 통한 빠른 액세스
중간값 추가 삭제 느림
끝에 값 추가 삭제는 빠름.
--------------------------------------------------------------------------------
list Doubly linked list 어떤 위치에서건 추가 삭제 빠름.
양끝에서부터 액세스 빠름.
임의 접근 느림
--------------------------------------------------------------------------------
deque vector와 유사 , 빠른 임의접근(인덱스번호 이용)
그러나 양끝에서 중간값 추가삭제 느림
액세스 가능 양끝에서 빠른 추가삭제(push & pop)
================================================================================
STL을 생성하기는 쉽지만 반드시 헤더를 include해야 한다.
예)
vector< int> aVect; // ints 벡터 생성 (K:ints = integer)
또는
list< airtime> depature_list; // airtime 리스트 생성
예제에서 주목할 것은 STL 콘테이너의 크기를 지정하지 않는 다는 것이다. 콘테이너는
스스로 메모리를 할당하게 된다.
1.2 Associate Containers
associate 콘테이너는 sequential이 아니다. 대신에 키를 이용하여 데이터를 액세스한다.
어떤 저장된 요소들을 특별한 순서로 정렬하기 위해 콘테이너가 자동으로 키를
사용한다. 이것은 알파벳 순으로 정렬된 단어를 찾아보는 보통 영어 사전과 같다.
키값을 주면 콘테이너는 이 값을 요소의 메모리 위치로 변환한다. 키만 알고 있다면
신속하게 연관된 값을 알 수 있다. STL에는 set과 map의 두 종류의 associative
콘테이너가 있다. 이것들은 모두 빠른 검색,추가,삭제를 제공하는 트리 구조로
데이터를 저장한다. set과 map은 애플리케이션의 다양한 데이터를 저장하는데 적합한
매우 융통성 있는 데이터 구조이다. 그러나 정렬과 임의접근에 필요한 다른 오퍼레이션은 비효율적이다.
set은 map보다 쉽고 주로 이용된다. set은 키를 포함한 아이템 갯수를 저장한다.
키는 아이템을 정렬하기 위한 속성이다. 예를 들면, set으로 person오브젝트를 저장할때
이름속성을 키값으로 정렬해서 저장할 수 있다. 이런 경우에 이름으로 검색을 해서
빠르게 위치를 알 수 있다. int같은 기본 타입을 저장한다면, 키는 int 같은 기본 타입을
통째로 저장된다. 때론 키값으로 set에 저장된 전체 오브젝트를 참조한다.
그러나 이것은 데이터를 정렬하기 위해 사용된 the 속성을 강조하기 위한 키 오브젝트라
부르지 않는다. 키는 전체 아이템이 아니다.
map은 키(key)와 값(value) 오브젝트 쌍을 저장한다. map은 종종 배열처럼 사용되지만
배열의 인덱스로 엘리먼트를 참조하지 않고, 임의 타입을 인덱스로 사용한다.
다시말해서 키 오브젝트가 인덱스 역할을 하고 value 오브젝트가 인덱스가 가리키는 값이 된다.
set과 map 콘테이너는 저장된 각 데이터에 대해서 오직 하나의 키만을 제공한다.
반면에 multimap과 multiset 콘테이너는 mulitple key를 제공한다.
Table 1.2 Basic Associative Containers
================================================================================
특징
================================================================================
set 키 오브젝트만 저장하고 각 value에 대해서 하나의 키만 제공.
--------------------------------------------------------------------------------
multiset 키 오브젝트만 저장하지만 복수개의 키를 제공.
--------------------------------------------------------------------------------
map value와 키를 연관시킴. 각 value에 대해 하나의 키만 제공.
--------------------------------------------------------------------------------
multimap value와 키를 연관시킴. 복수개의 키를 제공.
================================================================================
associative 콘테이너의 생성은 sequential 콘테이너처럼 하면된다.
set< int> intSet; // ints의 set을 생성
또는
multiset< employee> machinists; // employee의 multiset을 생성
1.3 Member Functions
알고리즘은 sorting이나 searhing 같은 복잡한 오퍼레이션의 결과를 도출하기 위해
STL에서 가장 많이 사용되지만 콘테이너도 간단하게 특별한 타입의 작업을
수행하기 위해서 멤버 함수가 필요하다.
테이블 1.3은 대부분의 콘테이너에 공통으로 사용되는 멤버함수를 나타낸 것이다.
Table 1.3 Some Member Functions Common All Containers
================================================================================
이름 목적
================================================================================
size() 콘테이너에 있는 아이템의 갯수를 리턴
--------------------------------------------------------------------------------
empty() 콘테이너가 empty면 true를 리턴
--------------------------------------------------------------------------------
max_size() 콘테이너가 확장 가능한 최대 크기 리턴
--------------------------------------------------------------------------------
begin() 콘테이너의 시작점을 iterator로 리턴, forward iteration
--------------------------------------------------------------------------------
end() 콘테이너의 끝점을 iterator로 리턴, end forward iteration
--------------------------------------------------------------------------------
rbegin( ) backword iteration을 위해 역으로 시작하는 iterator를 리턴
--------------------------------------------------------------------------------
rend() rbegin()의 반대 끝을 iterator로 리턴
================================================================================
이 밖에도 각각 콘테이너들이 멤버 함수들을 가지고 있지만 뒤로 미루도록 한다.
1.4 Container Adapters
container adapter라 불리는 앞에서 언급한 일반적인 콘테이너로 부터 특별한 목적을 갖는
콘테이너를 만드는 것도 가능하다. 이러한 특별한 목적을 가지는 콘테이너들은
일반적인 콘테이너들 보다도 더 간단한 인터페이스를 가지게 된다.
STL에서 콘테이너 어댑터로 구현된 특별화된 콘테이너로는 stacks, queues, 그리고
priority queues등이 있다. 우리에게 친숙한 스택은 스택의 top에서 혹은 bottom에서
pushing과 popping이 제한된 액세스로 구성된다. 큐에선 한쪽 끝에서 아이템을 push하고
다른 한쪽에서 그 아이템을 pop하게 되어 있다. 그리고 우선순위 큐(priority queue)
에서는 임의 순서로 전방에서 아이템을 push 가능하지만 다른 한쪽에서 그 아이템을
pop할 때는 항상 가장 큰 아이템 부터 pop하게 된다(우선순위 큐는 자동으로 정렬을 한다).
deque가 자주 사용되긴 하지만 스택, 큐, 우선순위 큐는 서로 다른 sequence 콘테이너로부터
생성된다. 테이블 1.4는 추상 데이터 타입(abstract data type)과 구현에 사용된
시퀀스 콘테이너들을 보여준다.
Table 1.4 Adapter-Based Containers
================================================================================
Container Implementation
Characteristics
================================================================================
stack vector, list, deque로 구현
한쪽 끝에서만 push/pop 가능
--------------------------------------------------------------------------------
queue list, deque로 구현
한쪽에서 push, 다른쪽에서 pop
--------------------------------------------------------------------------------
priority_queue vector나 deque로 구현
한쪽에서 임의 순서로 push, 다른 쪽에서 저장된 순서로 pop
================================================================================
중첩된 템플릿 선언으로 클래스를 만드는 것도 가능하다. 예를 들어 ints 오브젝트를
갖는 deque클래스로 스택을 만든다고 할때,
stack < deque< int>> aStack;
가 가능하다. 위 선언을 할 때 주의할 것은 ">>" 사용에 유의해야 한다는 것이다.
stack < deque< int>> aStack; // syntaxerror
처럼 선언하면 syntax error가 발생할 것이다. 이유는 컴파일러가 >> 를 shift 연산자로
파싱하기 때문이다. 반대의 경우인 "< < " 의 사용도 마찬가지 이다.
1.5 Algorithms
알고리즘은 콘테이너(또는 콘테이너들)에 있는 데이터를 가지고 뭔가를 하는 함수들이다.
이미 알고 있듯이 STL에서 알고리즘은 콘테이너 클래스의 멤버 함수도 아니고 프랜드 함수도
아니다. 그렇지만 이것 또한 템플릿 함수들이다. 따라서 C+
+ buit-in 배열을 사용 할수 도 있고
사용자가 만든 콘테이너 클래스를 사용할 수도 있다.
테이블 1.5는 몇가지 알고리즘들을 나타낸다.
Table 1.5 Some Typical Algorithms
================================================================================
Algorithm Purpose
================================================================================
find 어떤 값과 동일한 최초의 값을 리턴
--------------------------------------------------------------------------------
count 엘리먼트의 갯수를 리턴
--------------------------------------------------------------------------------
equal 두 콘테이너의 데이터가 완전히 같으면 true를 리턴
--------------------------------------------------------------------------------
search 다른 콘테이너에서 같은 시퀀스 value의 시퀀스를 찾는다.
--------------------------------------------------------------------------------
copy 시퀀스 값을 복사
--------------------------------------------------------------------------------
swap 값을 서로 바꾼다.
--------------------------------------------------------------------------------
iter_swap 시퀀스 값을 서로 바꾼다.
--------------------------------------------------------------------------------
fill 시퀀스의 특정 위치에 값을 채운다.
--------------------------------------------------------------------------------
sort 지정한 순서로 정렬을 한다.
--------------------------------------------------------------------------------
merge 두개의 저장소를 결합한다.
--------------------------------------------------------------------------------
accumulate 주어진 범위의 값을 더한다.
--------------------------------------------------------------------------------
for_each 각 콘테이너의 엘리먼트 값을 탐색
================================================================================
다음의 데이터를 가지는 int 타입의 배열을 생성했다고 가정해보자
int arr[8] ={ 42, 31, 7, 80, 2, 26, 19, 75};
STL의 sort 알고리즘을 써서 다음처럼 정렬할 수 있다.
sort (arr, arr+
8);
위에서 arr은 arr의 시작 주소이고 arr+
8은 끝 주소이다.
iterator는 포인터 비슷하게 콘테이너에 있는 아이템(엘리먼트)을 액세스하는 엔티티이다.
주로 콘테이너에서 반복(iterating)에 의해 엘리먼트에서 엘리먼트로 선형 이동하는데
사용된다. +
+오퍼레이터를 사용해서 이터레이터를 증가하면 이터레이터는 다음 엘리먼트를
가리키게 된다. 그리고 역참조(dereference)연산자 *로 이터레이터가 가리키고 있는
데이터를 참조하게 된다. STL에서 이터레이터는 iterator 클래스로 표현된다.
콘테이너에 따라서 서로다른 이터레이터를 사용해야며 이터레이터의 주요 3 가지 종류는
forward, bi-directional, random access 이다.
forward 이터레이터는 콘테이너상에서 한번에 한아이템씩 전방 이동 밖에 할수 없다.
즉, +
+오퍼레이터만 있으면 된다. 절대 후방으로 이동할 수 없고 콘테이너 중간위치로의
임의 설정을 할 수 없다. bidirectional 이터레이터는 전방/후방으로 모두 이동가능하다.
따라서 +
+오퍼레이터와 --오퍼레이터가 모두 정의되어 있다. random access 이터레이터는
전방/후방 이동뿐만 아니라 임의 위치로 점프할 수 있다.
1.6 Iterator Characteristics
================================================================================
Read/Write 저장가능 방향 Access
================================================================================
Random Access R/W Yes 전방/후방 Random
--------------------------------------------------------------------------------
Bidirectional R/W Yes 전방/후방 Linear
--------------------------------------------------------------------------------
Forward R/W Yes 전방 Linear
--------------------------------------------------------------------------------
Output W No 전방 Linear
--------------------------------------------------------------------------------
Input R No 전방 Linear
================================================================================
1.7 STL의 잠재적 문제(PotentialProblems with STL)
STL의 복잡성으로 인해서 컴파일러에게 무리를 주어 (K:원문이 이렇게 써있는대 아마도 잘못 쓴거 같음)
먼저, 헤더화일에 포함되어 있어서 에러 찾기가 매우 어렵다. 해결책은 유저 코드의
매 라인에 주석을 달아 놓는 길뿐이다. 프리컴파일드 헤더의 사용으로 컴파일 속도가
엄청나게 빨라지지만 STL을 사용하면 컴파일 시간을 느려지게 한다.
STL은 아마도 유효한 숫자가 소실 된다고 경고를 내지만 이 경고는 무시하던지 disable 시켜버리면 된다.
2 Algorithms
STL알고리즘은 데이터 콜렉션상에서 어떤 오퍼레이션을 수행한다. 이러한 알고리즘들은
STL콘테이너와 동작하도록 디자인 되어있지만 보통 C+
+ 배열에도 적용 시킬 수 있다.
배열 프로그래밍을 할때 상당부분의 노가다를 줄일 수 있다.
2.1 The find()Algorithm
find() 알고리즘은 어떤 값들을 가진 콘테이너에서 일치하는 첫번째 엘리먼트를 찾는데
사용한다. FIND 예제 프로그램은 ints배열에서 어떻게 찾는지를 보여준다.
// find.cpp
// finds the first object with a specified value
#include< iostream>
#include< algorithm> // forfind ()
using namespace std;
int arr[] ={ 11, 22, 33, 44, 55, 66, 77, 88};
int main ()
{
int *ptr;
ptr = find (arr, arr+
8, 33); // 맨 처음 나타나는 33을 찾는다.
cout << “33을 가지는 첫번째 오브젝트가 발견된 위치는” << (ptr-arr) << endl;
return 0;
}
(K:VC+
+에서 한다면 return 0; 바로전에 getchar(); 같은걸 써주거나 브래이크 포인트를 찍어야함.)
결과
33을 가지는 첫번째 오브젝트가 발견된 위치는 2
이 프로그램에서 algorithm 화일을 인클루드했는데, 여기서 주의할것은 STL에서
헤더화일들은 확장자(.H나 .CP같은P)를 갖지 않는다는 것이다.
find()함수에서 처음 두개의 파라미터는 범위를 가리킨다. 위의 예에서 C+
+ 포인터를
사용하였는데 이런 경우는 이터레이터에서 특별한 경우이다. 첫번째 파라미터는 첫번째
값을 가리키는 이터레이터(포인터)이고 두번째 파라미터는 마지막 엘리먼트를 말한다.
위 예제를 C+
+의 for 루프로 다시 표현하면
for (int j=0; j< 8; j+
+) // from 0to 7
{
if (arr[j] == 33)
{
cout << “33을 가지는 첫번째 오브젝트가 발견된 위치는” << j << endl;
break;
}
}
FIND 예제의 find()는 for 루프를 작성하는 불편함을 없애준다. 좀더 복잡한 상황에서
알고리즘은 아주 복잡한 코드를 작성하는 일을 피하게 해준다.
2.2 The count()Algorithm
count()는 콘테이너에서 특정한 값을 가지는 엘리먼트가 몇개 있는지 알아본다.
COUNT 예제를 보자.
//count.cpp
// countthe number of objects with a specified value.
#include< iostream>
#include< algorithm> // forcount()
using namespace std;
int arr[] ={ 33, 22, 33, 44, 33, 66, 77, 88};
int main()
{
int n =count (arr, arr+
8, 33); // 33의 갯수를 센다.
cout << “33이 ” << n << “개 있습니다.” << endl;
return 0;
}
결과:
33이 3개 있습니다.
2.3 The sort() Algorithm
아마 sort() 알고리즘은 짐작이 갈것이다. 바로 SORT 예제를 보자.
// sort.cpp
// integers 배열을 정렬한다.
#include< iostream>
#include< algorithm>
using namespace std;
int arr[] ={ 45, 2, 22, -17, 0, -30, 25, 55};
int main()
{
sort (arr, arr+
8);
for (int j=0; j< 8; j+
+)
cout << arr[j] << " ";
return 0;
}
결과:
-30 -17 0 2 22 25 45 55
2.4 The search() Algorithm
어떤 알고리즘은 한번에 두개의 콘테이너로 작업한다. find()알고리즘은
싱글 콘테이너에서 찾아보는 것이지만, search()알고리즘은 어떤 한 콘테이너의
연속되는 값들(패턴)을 다른 한 콘테이너에서 찾아본다.
// search.cpp
#include< iostream>
#include< algorithm>
using namespace std;
int source [] = {11, 44, 33, 11, 22, 33, 11, 22, 44};
int pattern[] ={11, 22, 33};
int main ()
{
int *ptr;
ptr = search(source,source+
9, pattern,pattern+
3);
if (ptr == source+
9)
cout << “No match foundn”;
else
cout << “Match at ” << (ptr-source) << endl;
return 0;
}
결과:
Match at 3
이터레이터 값 ptr이 source의 끝까지 갔다면 찾지 못했다는 소리이다.
2.5 The merge() Algorithm
3개의 콘테너를 가지고 작업을 하는 알고리즘으로써 2개의 소스 콘테이너를 합하여
목적 콘테이너에 넣는다.
// merge.cpp
#include< iostream>
#include< algorithm>
using namespace std;
int src1[] = { 2, 3, 4, 6, 9 };
int src2[] = { 1, 3, 5 };
int dest[8];
int main()
{
// src1과 src2를 dest로 merge한다.
merge (src1, src1+
5, src2, src2+
3, dest);
for (int j=0; j< 8; j+
+)
cout << dest[j] << " ";
cout << endl;
return 0;
}
결과 :
1 2 3 3 4 5 6 9
결과를 보면 알수 있듯이 merge는 순서를 유지한다.
2.6 Function Objects
어떤 알고리즘들은 인자 함수 객체를 받는 것이 있다. 함수객체는 겉으로 보기엔
템플릿 함수처럼 보인다. 그러나 실체는 ()오퍼레이터를 오버로드한 싱글 멤버함수를
(K:오버로드란 클래스에서 연산자를 함수처럼 사용하는 것을 말한다. Ex : C.Add(); -> C+
+;)
가진 템플릿 클래스 객체이다. 이것은 이해가 잘 안되지만 사용하기가 아주 편리하다.
배열을 ascending(K:오르는)이 아닌 descending으로 정렬하고자 한다고 하고 아래 예제를 보자.
// sortemp.cpp
#include<iostream>
#include<algorithm>
#include<functional>
usingnamespace std;
double fdata[] ={ 19.2, 87.4, 33.6, 55.0, 11.5, 42.2 };
int main()
{
sort ( fdata, fdata+
6, greater<double>());
for (int j=0; j< 6; j+
+)
cout << fdata[j] << " " << endl;
return 0;
}
sort() 알고리즘은 일반적으로 ascending order로 정렬하지만 greater< >() 함수객체는
정렬순서를 뒤집는다.
따라서 결과는
87.4 55 42.2 33.6 19.2 11.5
이 외에도 산술 이나 논리를 위한 함수객체가 더 있으나, 뒤에서 자세하게 보기로 한다.
2.6.1 사용자 정의 함수 객체
함수객체는 이미 정의되어 있는 오퍼레이터들(+
,< ,==,..)과 함께 기본 C+
+ 타입들과
클래스만 적용이 된다. 그런데 이러한 경우가 아니라면, 즉 사용자가 작성한 함수를
함수객체로 쓰고자 한다면 어떻게 해야하지? 예로, < 오퍼레이터가 정의되지 않은
char* 문자열 비교를 위해서 비교함수를 함수 주소를 넘겨줌로써 함수객체 대신
사용할 수 있다. 아래의 코드를 보자.
// sortcmp.cpp
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
char *names[] ={"Geroge", "Penny", "Estelle", "Don", "Mike", "Bob"};
bool alpha_comp (char*, char*);
int main()
{
sort (names, names+
6, alpha_comp);
for (int j=0; j< 6; j+
+)
cout << names[j] << endl;
return 0;
}
bool alpha_comp (char *s1, char* s2) // s1< s2이면 true를 리턴
{
return ( strcmp ( s1, s2) < 0 ) ? true : false;
}
sort()에서 3번째 인자는 alpha_comp 함수의 주소이다. 위의 결과는
Bob
Don
Estelle
George
Mike
Penny
이다. 즉 이름을 알파벳 순으로 정렬한 결과이다.
2.7 Adding _if to Algorithms
알고리즘중에는 끝에 _if가 붙은 알고리즘이 있다. 이러한 알고리즘들은 함수 객체나
함수인 속성(predicate)이라 불리는 추가 파라미터를 가진다. 예로 find()알고리즘은
어떤 특정한 값과 일치하는 값을 찾는다. 또한 find_if()알고리즘으로도 어떤 추상적
특징을 가지는 엘리먼트를 찾게 할 수도 있다. 아래의 예제 코드는 string객체를 사용한다.
find_if()알고리즘은 string 오브젝트 배열에서 Don이란 값을 가지는 string을 찾는
isDon() 함수의 supprt를 받는다. 아래는 FIND_IF의 코드이다.
// find_if.cpp
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
string names[] ={“Geroge”,”Estelle”, “Don”, “Mike”, “Bob”};
/////////////////////////////////////////////////////////////////
bool isDon(string name)
{
return name == "Don";
}
/////////////////////////////////////////////////////////////////
int main()
{
string* ptr;
ptr = find_if (names, names+
5, isDon);
if (ptr == names+
5)
cout << "Done is not on the list" << endl;
else
cout << "Don is element " << (ptr-names)
<< " on the list." << endl;
return 0;
}
/////////////////////////////////////////////////////////////////
Don은 실제 list에 있기때문에 프로그램의 결과는
Don iselement 2 on the list.
이다.
여기서 find_if()는 모든 엘리먼트를 isDon()으로 넘겨주고 isDon은 Don을 찾으면
true를 리턴하게 되고 find_if를 빠져나오게 된다.
(K:Find()문과 마찬가지로 3번째에 있으니 시작이 0이므로 2라고 표시된다. )
이밖에도 _if버전으로 적용될수 있는 count(), replace(),remove()등등이 있다.
2.8 The for_each()Algorithm
for_earch()는 콘테이너에 있는 모든 엘리먼트를 상대로 뭔가를 할 수 있는
알고리즘이다. 그리고 또한 사용자가 직접 뭔가를 수행하기 위한 코드를 작성할 수 있다.
사용자 작성 함수는 값을 바꿀수는 없지만 참조와 display는 할 수 있다.
다음 예제는 인치를 센티미터로 바꾸고 보여주는 것이다. 2.54를 곱하는 in_to_cm()함수를
만들고 for_each() 의 세번째 파라미터로 주소를 넘겨준다.
// for_each.cpp
#include<iostream>
#include<algorithm>
using namespace std;
void in_to_cm(double);
int main()
{
double inches[] ={ 3.5, 6.2, 1.0, 12.75, 4.33 };
for_each (inches,inches+
5, in_to_cm);
cout << endl;
return 0;
}
void in_to_cm(double in)
{
cout << (in * 2.54) << " ";
}
결과는
8.89 15.748 2.54 32.385 10.9982
2.9 The transform() Algorithm
transform()알고리즘은 콘테이너의 모든 엘리먼트에게 뭔가를 하고 다른 콘네이너에게
(또는 같은 콘테이너에게)이 결과를 전송한다. 사용자 작성함수는 엘리먼트에게
뭘할건지를 결정한다. 그리고 이 사용자 작성함수의 리턴값은 입력파라미터와 일치해야 한다.
다음의 예는 FOR_EACH와 비슷하지만 결과를 다른 double []로 저장한다.
// tranfo.cpp
#include<iostream>
#include<algorithm>
using namespace std;
double in_to_cm(double in)
{
return in * 2.54;
}
int main()
{
double inches[] ={ 3.5, 6.2, 1.0, 12.75, 4.33 };
double centi[5];
double in_to_cm(double); // prototype
transform (inches, inches+
5, centi, in_to_cm);
for (int j=0; j<5; j+
+)
cout << centi[j] << " " << endl;
return 0;
}
결과는 FOR_EACH와 같다.
이제까지 단지 몇개의 알고리즘만 살폈지만 STL에는 이밖에도 아주 많은 알고리즘이
있다. 여기서는 단지 어떻게 사용하고 응용가능한지 만을 보인 것 뿐이다.
3 Sequential Containers
이미 앞에서 살폈듯이 STL 콘테이너에는 2가지 주요 카테고리로써 sequence 콘테이너
와 associative 콘테이너가 있다고 했다. 이번 장에서는 vector, list,deque의 3가지
시퀀스 콘테이너를 소개하고 이 콘테이너들이 어떻게 작동하고 멤버함수를 어떻게
사용하는지를 다룰 것이다. iterator에 대해 아직은 자세하게 배우지 않았기 때문에
위의 콘테이너로 어떤 동작을 수행하게 하는 것은 다루지 않는다. iterator에 대해서는
다음 장에서 자세하게 다루도록 한다. 앞으로 나올 예제들은 콘테이너를 설명하기
위해 몇가지 멤버함수들만 사용하게 될것이다. 기억해둘 것은 서로 다른 콘테이너들도
같은 이름의 멤버함수를 갖는다는 사실이다.
3.1 Vectors
벡터는 영리한 배열(smart array)이라 생각해도 좋다. 벡터는 저장소를 할당하고,
데이터의 삽입이나 삭제에 따라 크기를 확장하거나 축소시킨다. 벡터를 []오퍼레이터를
써서 배열처럼 사용해도 무방하다. 벡터에서 이러한 임의접근은 매우 빠르게 작동 한다.
또한 push를 이용해서 벡터의 끝에 데이터를 빠르게 추가한다. 데이터가 추가되면
(추가되기 직전에) 크기도 자동으로 증가한다.
3.1.1 Member Functions push_back(),size(), and operator[]
첫번째 예제로 VECTOR는 일반적인 벡터 오퍼레이션을 보여준다.
//vector.cpp
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector< int> v; // ints 벡터를 생성한다.
v.push_back(10); // 배열의 끝에 데이터를 추가한다.
v.push_back(11); v.push_back(12); v.push_back(13);
v[0] = 20; v[3] = 23; // 새로운 값으로 대치
for (int j=0; j< v.size(); j+
+) // 벡터의 내용물을 출력
cout << v[j] << " " ;
return 0;
}
벡터 v를 생성하기 위해 벡터의 기본생성자(no-argument)를 사용했다.
콘테이너의 크기를 알수 없으므로 0부터 시작한다. push_back()멤버함수는 벡터의
끝에 값을 추가한다. list나 queue처럼 벡터의 시작부(index=0)에는 데이터를 삽입
할 수 없다. 예제에선 10, 11, 12, 13을 추가 했으므로 v[0]은 10, v[1] = 11,
v[2] = 12, v[3]=13이다. 일단 벡터에 데이터가 들어가면 [] 오퍼레이터를 통해
읽고 쓰기가 가능해진다. 예제에서 첫번째 값을 v[0]=20으로 바꾸고, v[3] = 23으로
바꿨다. 따라서 예제의 결과는
20 11 12 23
이 된다.
size() 멤버함수는 벡터의 엘리먼트 갯수를 리턴한다.
예제에서 다루지 않은 다른 멤버함수로는 max_size()가 있다. 이 함수는 콘테이너가
확장가능한 최대 크기를 리턴한다. 이 수치는 콘테이너의 데이터 타입에 따라 틀려지
게 된다. vector< int>의 max_size()는 1,073,741,823을 리턴할 것이다.
3.1.2 Member Functions swap(),empty(), back() and pop_back()
다음의 VECTOR는 벡터의 추가적인 생성자와 멤버함수에 대해서 다룬다.
//vectcon.cpp
#include< iostream>
#include< vector>
using namespace std;
int main()
{
double arr[] ={1.1, 2.2, 3.3, 4.4};
vector< double> v1(arr,arr+
4); // 벡터를 arr로 초기화 시킨다.
vector< double> v2(4); // 크기가 4인 empty 벡터를 생성한다
v1.swap (v2); // v1과 v2의 값을 맞바꾼다.
while (!v2.empty()) // v2가 빌(empty)때 까지
{
cout << v2.back() << " "; // 마지막 엘리먼트를 display
v2.pop_back(); // 마지막 엘리먼트를 삭제
}
return 0;
}
위에서 두가지 새로운 백터의 생성자를 사용하였다. 첫번째 벡터 v1은 보통 C+
+ 배열
을 가지고 초기화 시켰고, 두번째 벡터 v2는 크기만 결정하였다.
swap()멤버 함수는 엘리먼트의 order를 유지하면서 모든 엘리먼트를 바꾸게 된다.
이 프로그램에서 v2와 v1의 값을 바꿨기 때문에 v2를 display시켜보면 결과는
4.4 3.3 2.2 1.1
이 된다.
back() 멤버함수는 벡터의 맨 끝에 있는 엘리먼트를 리턴한다. pop_back() 멤버함수는
벡터의 맨 끝에 있는 엘리먼트를 제거한다. 따라서 루프는 메번 다른 엘리먼트를 출력
하게 된다. swap() 같은 멤버함수는 또한 알고리즘에도 존재한다. 이런 경우는 알고리즘
버전보다도 해당 콘테이너의 멤버함수가 더 효율적이기 때문에 사용된 것이다. 때때로
알고리즘의 swap()을 사용해서 서로다른 콘테이너의 엘리먼트들을 교환할수도 있다.
3.1.3 Member Functions insert()and erase()
insert()와 erase()멤버함수는 콘테이너상의 임의의 위치에 엘리먼트를 삽입하고 삭
제 한다. 이러한 함수들은 벡터를 사용할 경우 insert나 remove를 위해 모든 엘리먼트
들을 이동시켜야 하기 때문에 매우 효율적이지 못하다. 그러나 삽입과 삭제는 그럼에도
불구하고 속도외에는 효율적인 방법이다. 이번 예제는 멤버함수를 어떻게 사용하는
지를 보인다.
//vectins.cpp
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int arr[] ={100, 110, 120, 130};
vector< int> v(arr, arr+
4); // 벡터를 arr로 초기화 시킨다.
cout << "Before insertion : ";
for (int j=0; j< v.size(); j+
+)
cout << v[j] << " ";
cout << endl;
v.insert (v.begin()+
2, 115);
cout << "After insertion : ";
for (j=0; j< v.size(); j+
+)
cout << v[j] << " ";
cout << endl;
v.erase (v.begin()+
2);
cout << "After erasure : ";
for (j=0; j< v.size(); j+
+)
cout << v[j] << " ";
cout << endl;
return 0;
}
결과:
Before insertion: 100 110 120 130
After insertion: 100 110 115 120 130
After erasure: 100 110 120 130
3.2 Lists
STL의 list 콘테이너는 doubly linked list로써, 각 엘리먼트들은 다음 엘리먼트의
포인터를 가지고 있을 뿐만 아니라 이전 엘리먼트의 포인터도 가지고 있다. 콘테이너는
양쪽으로 빠른 접근을 위해 엘리먼트의 앞쪽과 뒤쪽 모두를 저장하고 있다.
3.2.1 Member Functions push_front(),front(), and pop_front()
첫번째 예제인 LIST는 데이터를 삽입하고 읽어오고 빼내는 방법을 보여준다.
// list.cpp
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> ilist;
ilist.push_back(30); ilist.push_back(40); ilist.push_front(20); ilist.push_front(10);
int size = ilist.size();
for (int j=0; j<size; j+
+) {
cout << ilist.front() << " ";
ilist.pop_front();
}
cout << endl;
return 0;
}
데이터를 위,앞에서 추가 한다음 앞쪽부터 display하고 삭제하였다. 결과는
10 20 30 40
이다.
push_front(),pop_front(),front()멤버함수는 이미 앞에서 다룬 벡터의 push_back(),
pop_back(), back()과 유사하다. (K:다만 콘테이너의 앞부분을 처리 한다는것만 다르다)
리스트는 랜덤 액세스 속도가 매우 느리기 때문에 리스트로 랜덤 액세스는 피해야 함을 주목해야한다.
이러한 이유때문에 리스트에는 [] 오퍼레이터가 없다. 정말로 랜덤 액세스가 필요하면
vector나 deque를 사용하기 바란다. 리스트는 중간에 엘리먼트를 삽입 삭제가 빈번하게 발생할 때 유용하다.
엘리먼트의 추가, 삽입을 위해 모든 엘리먼트의 포인터를 이동시켜야 하기 때문에
vector나 deque에 비해 효율성은 떨어지지만 포인터만 이동하므로 빠른편에 속한다.
(문제점은 추가,삭제 포인터를 찾는데 시간이 걸린다는 것이다)
3.2.2 Member Functions reserve(),merge(), and unique()
어떤 멤버함수들은 list에만 있는 것이 있지만, 결과적으로 같은 기능을 한다. 예제
LISTPLUS는 이러한 함수들에 대해 다룬다.
// listplus.cpp
#include<iostream>
#include<list>
using namespace std;
int main()
{
int j;
list <int> list1, list2;
int arr1[] = {40, 30, 20, 10 };
int arr2[] = {15, 20, 25, 30, 35 };
for (j=0; j<4; j+
+)
list1.push_back( arr1[j]);
for (j=0; j<5; j+
+)
list2.push_back( arr2[j]);
list1.reverse(); // list1의 순서를 뒤집는다.
list1.merge(list2); // list1에 list2를 포함시킨다.
list1.unique(); // 중복되는 값을 제거한다. 20, 30
int size = list1.size();
while (!list1.empty())
{
cout << list1.front() << " ";
list1.pop_front();
}
cout << endl;
return 0;
}
첫번째 리스트는 역순으로 입력되었기에 reverse()를 통해 정렬을 하였다(이 동작은
양 끝을 액세스 할 수 있기때문에 매우 빠르다.). merge()를 수행하기 위해서 먼저
두 리스트를 정렬해야만한다. 여기까지 결과는
10 20 30 40
15 20 25 30 35
이다. 이제 merge() 함수로 list1과 list2를 병합하면 정렬 순서에 따라 병합된다.
10 15 20 20 25 30 30 35 40
마지막으로 list1에 unique() 멤버함수를 적용시킨다. 이 함수는 근접한 엘리먼트들이
같은 값을 가질 경우 이를 제거하고 하나만 남긴다. 따라서 list1의 내용은
10 15 20 25 30 35 40
이 된다. 이 값을 display하기 위해 front()와 pop_front()를 이용하였다.
3.3 Deques
어떤 면에서 deque는 vector와 비슷하고 또 어떤 면에서는 list와 비슷하다. vector처럼
[] 오퍼레이터로 랜덤 액세스를 지원하고 뿐만 아니라 list처럼 엘리먼트를 앞뒤에서
액세스가 가능하다. 즉, deque는 push_front(), pop_front(), front()를 지원하는
double-ended vector의 일종이다.
그렇지만 메모리는 벡터와 큐와 다르게 할당이 된다. 벡터는 항상 연속된 메모리 공간에
할당이 된다. 벡터의 크기가 너무 커지면 크기가 알맞는 메모리 공간으로 옮겨야 한다.
반면에 deque는 비연속적인 메모리(called segment)에 저장될 수 있다. capacity()
멤버함수는 엘리먼트 벡터가 이동없이 할당될 수 있는 최대 크기를 리턴한다. 그렇지만
deque에서는 메모리가 이동될 필요가 없기때문에 capacity()가 정의되어 있지 않다.
//deque.cpp
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> deq;
deq.push_back(30); // push items on back
deq.push_back(40);
deq.push_back(50);
deq.push_front(20); // push items on front
deq.push_front(10);
deq[2] = 33; // change middle item
for (int j=0; j<deq.size(); j+
+)
cout << deq[j] << ' '; // display items
cout << endl;
return 0;
}
앞에서 이미 push_back(), push_front(), operator []를 배웠기 때문에 더 이상
설명이 필요없다. deque도 다른 콘테이너처럼 똑같이 작동한다. 결과는
10 20 33 40 50
이다.
4. Iterators
이터레이터는 조금 신비스럽게 보이며서 STL의 핵심이라 할 수 있겠다. 이번 장에서는
우선 스마트 포인터와 알고리즘-콘테이너를 연결해 주는 이터레이터의 두가지 역할에
대해 다루게 된다. 물론 사용법은 몇몇 예제를 통해 보게 될것이다.
4.1 Iterators as Smart Pointers
종종 콘테이너의 모든(또는 일부 영역) 엘리머트를 가지고 작업을 해야할 때가 있다.
콘테이너에 있는 각각의 엘리먼트를 display하거나 total을 구하기 위해 값을 더하기도
한다. 전통 C+
+ 배열에서 이러한 오퍼레이션들은 포인터를(또는 [] 오퍼레이터) 이용하여
구현되었다. 예를 들어 아래의 코드는 float 배열을 순회(iterates)하고 각 엘리머트의
값을 보여준다.
float * ptr = start_address;
for (int j=0; j<SIZE; j+
+)
cout << *ptr+
+;
포인터가 가리키고 있는 값을 가져오기 위해 * 오퍼레이터를 이용하였고, 다음 포인터를
가리키기 위해 +
+ 오퍼레이터를 이용하였다.
4.1.1 Ordinary Pointers Underpowered
그러나, 고급 콘테이너들에서, 평범한 C+
+ 포인터는 유용하지 못하다. 만일 콘테이너에
저장된 아이템이 연속(contigious) 메모리에 저장되어 있지 않다면 포인터 취급이 더욱더
복잡해질 것이다. 예를 들어, 링크드 리스트에서 다음 아이템으로 이동할때 우리는 다음
아이템이 바로 옆에 있다고 예측할 수가 없기때문에 처음부터 포인터를 따라서 해당 위치를
먼저 찾아가야만 한다.
또한 포인터 변수에 어떤 콘테이너의 엘리먼트 주소도 저장하기를 원한다.
이 주소를 이용하여 이후에 이 값을 액세스할 수 있기 때문이다. 콘테이너의 중간에서 값을
삽입하거나 삭제했을 경우 이 콘테이너에 무슨 일이 발생할까? 아마도 콘테이너의 콘텐츠가
재정렬 되지 않는 이상 연속성이 끊어지게 될것이다. 삽입이나 삭제시 저장된 포인터값의
변경을 신경쓰지 않아도 된다면 참 좋을 것이다.
이러한 문제에 대한 한가지 해결책은 "smart pointer"클래스를 만든는 것 뿐이다.
이런 종류의 클래스 오브젝트는 기본적으로 멤버변수를 전통적인 포인터로 감싼다. 그리고
+
+와 * 오퍼레이터들은 콘테이너에서 비록 엘리먼트가 비연속일지라도 어떻게 엘리먼트를
다룰지를 오버로드한다. 아래의 예제는 어떻게 구현하는지 골격만 나타낸것이다.
class SmartPointer
{
private:
float *p; // 평범한 포인터
public:
float operator*() {}
float operator+
+{}
};
void main()
{
...
SmartPointer sptr = start_address;
for (int j=0; j<SIZE; j+
+)
cout << *ptr+
+;
}
4.1.2 Whoes Responsibility? (누가 책임지지?)
SmartPointer 클래스는 콘테이너에 소속(embed) 되어야만 하나? 아니면 독립적이어야만 하나?
STL이 선택한 해결점은 SmartPointer(이터레이터라고 불리는)를 만들기 위해 클래스와
완전히 독립되게 만들었다(사실 템플릿화 한것이다).
4.1.3 인터페이스로써의 이터레이터(Iterators as an Interface)
이터레이터가 콘테이너에서 스마트 포인터처럼 작동하는 것 말고, 이터레이터는 STL에서
또 다른 중요한 기능을 제공한다. 그것은 바로 어떤 콘테이너가 어떤 알고리즘이 사용될 수
있는지를 결정할 수 있게하는 것이다. 이 기능이 왜 필요할까?
이론적 센스로 생각한다면 모든 콘테이너에 모든 알고리즘을 적용할 수 있을 것이라는 생각이
들것이다. 사실 많은 알고리즘들이 STL의 거의 모든 콘테이너에 적용할 수 있다. 그러나
어떤 콘테이너에게는 매우 비효율적(즉 slow)이 될 수도 있다. sort()알고리즘을 예로
들자면, 정렬을 하기 위해서는 랜덤액세스가 필요하다. 랜덤액세스가 지원되지 않는 콘테이너
에서는 무조건 처음부터 탐색해서 정렬을 시도해야만 할것이다. -_-; 비슷한 예로 reverse()
알고리즘은 뒤에서(backword)부터 액세스가 지원되어야 할 것이다.
이터레이터는 놀랍도록 멋진 방법으로 콘테이너에 적합한 알고리즘을 적용시킨다.
앞에서 말했듯이 콘테이너를 컴퓨터와 프린터를 연결하는 것처럼 케이블이라 생각해보자.
케이블 한쪽 끝에 콘테이너를 플러그하고 다른 한쪽엔 알고리즘을 플러그한다. 그렇지만
모든 케이블들이 모든 콘테이너를 플러그할 수는 없고 모든 알고리즘을 플러그 할수도 없다.
만일 주어진 콘테이너에서 너무 무리한 알고리즘을 사용하려 한다면, 알고리즘과 연결하기 위한
케이블(이터레이터)을 찾을 수 없다. 이걸 컴파일 해보면 아마 컴파일러 에러를 보게될것이다.
그렇다면 이스킴을 작동시킬려면 얼마나 많은 종류의 케이블(이터레이터)들이 필요하게 될까?
통털어서 5개만 있으면 된다. 그림 4.3은 이 5가지 카테고리를 보여준다. 그림은 정교하게 하기
위해 밑에서 부터 위로 정렬된 것이다. 그렇다고 상속관계는 아니다.
+
-----------------+
| Random Access |
+
-----------------+
|
+
-----------------+
| Bidirectional |
+
-----------------+
|
+
-----------------+
| Forward |
+
-----------------+
| |
+
-+ +
-+
| |
+
-------------+ +
-------------+
| Input | | Output |
+
-------------+ +
-------------+
그림 1.3 이터레이터 카테고리
만일 어떤 알고리즘이 reading하는데(writing제외) 콘테이너의 전방참조만 필요하다면
알고리즘과 콘테이너를 연결하기 위한 이터레이터는 Input 이터레이터가 될것이다.사실
Input 이터레이터는 콘테이너뿐만 아니라 파일에서 읽을때나 cin으로 읽을 때도 적용될 수
있다.
알고리즘이 콘테이너의 전방참조가 필요하고 읽는 것 대신에 쓰는게 필요하다면
output 이터레이터가 연결될 것이다. Output 이터레이터도 Input이터레이터와 마찬가지로
파일이나 cout으로 write할때 일반적으로 사용된다.
알고리즘이 콘테이이너의 전방순회를 하고 읽기/쓰기 둘다 지원하게 싶을 때는 forward
이터레이터를 사용한다.
알고리즘이 콘테이너의 전방/후방 순회를 모두 지원하게 하고 싶을 때는 bidirectional
이터레이터를 사용하면 된다.
마지막으로, 알고리즘이 콘테이너의 아무 엘리먼트를 즉시 사용할 수 있게 하고 싶을 때는
random 이터레이터를 사용한다. 랜덤 액세스 이터레이터는 아무 엘리먼트나 액세스 할 수 있는
배열과 비슷하다. 랜덤 액세스 이터레이터만이 다음과 같은 산술 오퍼레이터를 가지고 있다.
iter2 = iter1 + 7;
Table 1.7 Capabilities of Different Iterator Categories
========================================================================
Step Step Random
Forward Read Write Back Access
Iterator Type +
+ value=*i *i=value -- [n]
========================================================================
Random access x x x x x
------------------------------------------------------------------------
Bidirectional x x x x
------------------------------------------------------------------------
Forward x x x
------------------------------------------------------------------------
Output x x
-----------------------------------------------------------------------
input x x
========================================================================
위Table을 보면 알 수 있듯이 모든 이터레이터들이 전방순회를 하는 +
+오퍼레이터를 지원한다.
input 이터레이터는 이퀄표시 오른쪽에서 * 오퍼레이터를 사용할 수 있다.(왼쪽은 안된다)
value = *iter;
output 이터레이터는 * 오퍼레이터를 오로지 오른쪽에서만 쓸 수 있다.
*iter = value;
전방순회 이터레이터는 읽기/쓰기 모두 다룰 수 있고, 두방향(bidirectional) 이터레이터는
증감을 이용하여 순회할 수 있다. 랜덤 액세스 이터레이터는 간단한 산술 오퍼레이터인
- 나 + 처럼 [] 오퍼레이터를 써서 아무 엘리먼트나 즉시 액세스가 가능하다.
알고리즘은 항상 필요한 것보다 더 많은 기능을 가질 수 있다. 만일 전방순회가 필요할때,
두방향 이터레이터나 랜덤 액세스 이터레이터를 사용해도 무방하다.
4.2 Matching Algorithms with Containers
이터레이터는 알고리즘과 콘테이너를 연결해주기 때문에 케이블 개념으로 이터레이터를
사용했었다. 그럼 이제 상상의 케이블 양끝에 촛점을 맞춰보자. (양끝은 콘테이너측과 알고리즘
측을 얘기한다)
4.2.1 이터레이터와 콘테이너의 연결 (Plugging the cable into a Container)
만약 스스로 기본 STL 콘테이너 범위로만 한정하여 생각하고 있다면, 단 두가지 이터레이터만
쓸 수 있을 것이다. 아래의테이블 1.8을 보면 벡터와 큐는 어떤 이터레이터든 수용할 수 있고,
반면에 list, set, multiset, map, multimap은 랜덤 이터레이터를 제외한 다른 것들만 수용할 수
있음을 알수 있다.
Table 1.8 Iterator Types Accepted by Containers
========================================================================
vector List Deque Set Multiset Map Multimap
========================================================================
Random access x x
------------------------------------------------------------------------
Bidirectional x x x x x x x
------------------------------------------------------------------------
Forward x x x x x x x
------------------------------------------------------------------------
Output x x x x x x x
------------------------------------------------------------------------
input x x x x x x x
========================================================================
그렇다면 STL은 어떻게 주어진 콘테이너에 알맞는 이터레이터를 수행할 수 있을까?
프로그래머는 이터레이터를 정의하고, 반드시 어떤 콘테이너가 사용할것인지를 명시해줘야만
한다. 예를 들어, int형을 수용하는 리스트를 선언 한다고 하면,
lint<int> iList; // list of ints
라고 선언하고, 이 리스트를 위한 이터레이터를 선언하기 위해 다음처럼 할것이다.
list<int>::iterator iter; // iterator to list-of-ints
이렇게 해주면, STL은 이 이터레이터를 자동으로 list에게 요구되는 bidirectional(쌍방)
이터레이터로 만들어 준다. vector나 deque에 대한 이터레이터는 자동으로 랜덤 액세스
이터레이터로 만들어 줄것이다.
이러한 자동선택 프로세스는 특정한 클래스에 적합하게하기 위해 보다 일반화된 클래스로부터
상속 받은 특정한 이터레이터 클래스를 가지게 함으로써 구현된다. 따라서 vector와 queue의
이터레이터는 random_access_iterator로부터 상속을 받고, 반면에 list의 이터레이터는
bidirectional_iterator 클래스로부터 상속된다.
그럼 이제 콘테이너가 다양한 이터레이터(케이블)의 끝과 어떻게 매치되는지를 살펴보자.
4.2.2 이터레이터와 알고리즘의 연결 (Plugging the Cables into th Algorithm)
이터레이터의 한쪽 끝이 어떻게 콘테이너와 연결되는지를 앞에서 봤으니까 이제 이터레이터의
다른 한쪽을 볼 차례다.
이터레이터가 알고리즘과 어떻게 연결될것인가?
모든 알고리즘들은 콘테이너의 엘리먼트들에게 무엇을 어떻게 할것인가에 따라서 이터레이터를
필요로 하게 된다. 알고리즘이 콘테이너의 엘리먼트에 대해 임의의 위치에서 액세스가 가능해야
하다면 랜덤 액세스 이터레이터가 필요하게 될것이다. 또는 이터레이터를 통해 단지 순차적 전방
액세스만 원한다면 덜 파워풀한 forward 이터레이터가 사용될 수 있을 것이다. 테이블 1.9는
알고리즘과 필요로하는 이터레이터를 나타낸다.
Table 1.9 Type of Iterator Representative Algorithms
=============================================================
Bidirec- Random
Input Output Forward tional Access
=============================================================
for_each x
-------------------------------------------------------------
find x
-------------------------------------------------------------
count x
-------------------------------------------------------------
copy x x
-------------------------------------------------------------
replace x
-------------------------------------------------------------
unique x
-------------------------------------------------------------
reverse x
-------------------------------------------------------------
sort x
-------------------------------------------------------------
nth_element x
-------------------------------------------------------------
merge x x
-------------------------------------------------------------
accumulate x
=============================================================
비록 각 알고리즘들이 어떤 한정적 이터레이터만 요구하더라고 이보다 더 파워풀한 이터레이터가
사용될 수 있다. replace() 알고리즘은 forward 이터레이터가 필요하지만 bidirectional(양방향)
이나 random access 이터레이터도 사용할 수 있다
.
이제 알고리즘이, 컴퓨터에 있는 케이블 코넥터처럼, 고정할 수 있는 핀을 가진 코넥터를
가졌다고 상상해보자. (그림 1.4) 랜덤 액세스를 필요로하는 것은 5개의 핀을 가졌고,
양방향(bidirectional) 액세스가 필요한 것은 4개의 핀이 있으며, 전방(forward) 참조를
요하는 것은 3개의 핀이 있다.
Random_access
container iterator Algorithm
+
----------+ |() -| +
------------+
| vector | ----|() -|----| reverse() |
+
----------+ |() OK -| +
------------+
|() -|
|()
bidirectional
iterator
+
----------+ |() -| +
------------+
| List |----|() -|----| sort() |
+
----------+ |() No -| +
------------+
|() Good -|
-|
[그림 1.4 콘테이너와 알고리즘을 연결하는 이터레이터]
알고리즘과 연결될 이터레이터의 끝은 몇 개의 구멍을 가진 코넥터를 가진다.
5개의 구멍을 가진 이터레이터는 5개의 핀을 가진 알고리즘과 연결되고, 또한 4개나 그 이하의
핀을 갖는 알고리즘과도 연결될 수가 있다. 그렇지만 4개의 구멍을 가지는 이터레이터
(bidirectional)와 5개의 핀이 있는 랜덤 액세스 알고리즘과는 연결 시킬 수 없다. 따라서 랜덤
액세스 이터레이터를 가지는 벡터와 데큐는 bidirectional 이터레이터를 지니고, 양방향
이터레이터 보다 덜 파워풀한 이터레이터와 붙을 수 있다.
4.3 The Tables Tell the Story
테이블 1.8과 1.9로부터 어떤 알고리즘이 어떤 콘테이너와 작동되는지를 이해할 수 있을것이다.
예를들어, 테이블 1.9는 sort() 알고리즘이 랜덤 액세스 이터레이터를 필요로 한다는 것을 알 수
있을 것이다. 테이블 1.8을 보면 랜덤 액세스 이터레이터가 벡터와 데큐만을 다룰 수 있음을
알 수 있다.
테이블을 보면 알 수 있듯이 sort()알고리즘을 list, set, map등에서 사용 할 수 없다.
이러한 콘테이너들이 랜덤 액세스 바로 밑 단계의 bidirectional 이터레이터를 사용하기 때문에
랜덤 액세스 이터레이터가 필요없는 어떤 알고리즘이든지 아무 STL 콘테이너와 작동이 된다.
(STL에서 singly-linked list는 forward 이터레이터만 사용하게 될것이다. 따라서 reverse()
알고리즘을 사용할 수 없게 된다.)
4.3.1 Overlapping Member Functions and Algorithms (멤버함수와 알고리즘의 중첩)
때때로 같은 이름을 가지는 멤버함수나 알고리즘 중에서 선택해야할 경우가 있다.
find()알고리즘은 input 이터레이터만 필요하다. 그래서 어떤 콘테이너든 사용이 가능하다.
그렇지만 set과 map은 자신만의 find() 멤버함수를 가지고 있다. 과연 어떤 버전의 find()를
사용할것인가? 일반적으로 멤버함수 버전이 있다는 것은 알고리즘이 그 콘테이너에 알고리즘이
비 효율적이기 때문에 따로 존재하는 것이다. 그러므로 이러한 경우엔 멤버함수를 사용하면 된다.
4.4 Iterators at Work
지금까지 이터레이터에 대해서 복잡하게 떠들었지만, 백문이불여일견! 직접 사용해보면
의외로 간단하다. 이미 앞에서 몇가지 예를 살펴보았듯이 일반적으로 사용되는 이터레이터들은
콘테이너의 begin(), end()가 이터레이터의 값을 리턴하게된다. 여태까지 이러한 함수들이
리턴하는 이터레이터 값이 포인터로만 취급된다고 말해었지만, 이젠 이러한 이터레이터들이
다른 함수에서 어떻게 사용되는지 확실하게 살펴보자.
4.4.1 Data Access
랜덤 액세스 이터레이터를 제공하는(vector, deque) 콘테이너에서는 [] 오퍼레이터를 이용하여
쉽게 엘리먼트를 참조했다. 랜덤 액세스를 지원하지 않는 list 같은 콘테이너는 이것과는 다른
방식의 참조 방법이 필요하다. 앞에서 들었던 예 중에 아이템을 하나씩 pop 해서 리스트의
내용을 display하기위해 "LIST"와 "LISTPLUS" 예제에서 destructive readout을 사용했었다.
이걸 좀더 심화하여 콘테이너에 정의된 이터레이터를 사용해보자.
// listout.cpp
// iterator and for loop for output
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = {2, 4, 6, 8 };
list<int> theList;
for (int k=0; k<4; k+
+) // 배열의 엘리먼트로 리스트를 채운다.
theList.push_back(arr[k]);
list<int>::iterator iter; // iterator to list-of-ints
for (iter=theList.begin(); iter != theList.end(); iter+
+)
cout << *iter << ' '; // display the list
cout << endl;
return 0;
}
이 프로그램은 단순히 theList 콘테이너의 내용을 보여주는 것이다. 결과는
2 4 6 8
이다.
위에서 콘테이너의 타입과 매치시키기위해 list<int>타입의 이터레이터를 선언했다.
마치 포인터를 사용하듯이 이터레이터를 사용하기 전에 먼저 초기값을 설정해야 한다.
for 루프에서 iter = theList.begin() 구문으로 리스트의 첫부분으로 초기화를 해주었다.
++오퍼레이터로 포인터를 증가시켜 *오퍼레이터로 이터레이터가 가리키고 있는 값을 역참조했다.
그리고 루프의 종료는 이터레이터가 콘테이너의 끝에 다다랐나를 체크함으로써 처리를 해주었다.
또한 for 루프와 기능적으로 동일하게 작동하게 하기 위해서 while 루프를 이용할 수도 있다.
iter = theList.begin();
while ( iter != theList.end() )
cout << *iter+
+ << ' ';
*iter+
+ 문법은 포인터와 같다.
4.4.2 Data Insertion
아래의 LISTFILL 예제를 통해 콘테이너의 엘리먼트가 이미 존재하는 곳에 데이터를 삽입하는
과정을 살펴보자.
// listfill.cpp
// uses iterator to fill list with data
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> iList(5); // empty list holds 5 ints
list<int>::iterator it; // iterator
int data = 0;
// fill list with data
for (it=iList.begin(); it != iList.end(); it+
+)
*it = data +
= 2;
// display the list
for (it=iList.begin(); it != iList.end(); it+
+)
cout << *it << ' ';
cout << endl;
return 0;
}
첫번째 for 루프는 콘테이너에 int 값은 2, 4, 6, 8, 10으로 채운다. 그리고 두번째 for
루프는 이 값들을 보여준다.
4.4.3 Algorithms and Iterators
우리가 얘기했던 알고리즘들은 인자로 이터레이터를 이용한다. 그리고 때론 값을 리턴하기도
한다. ITERFIND 예제는 find() 알고리즘이 리스트에 적용되는 걸 보여준다. (이미 알고있겠지만
find()알고리즘은 오직 input 이터레이터만 필요하기때문에 list와 함께 쓰일 수 있다.)
// ITERFIND.cpp
// find() returns a list iterator
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
int main()
{
list<int> iList (5); // empty list holds 5 ints
list<int>::iterator it; // iterator
int data = 0;
// fill list with data
for (it=iList.begin(); it != iList.end(); it+
+)
*it = data + 2; // 2, 4, 6, 8, 10
it = find (iList.begin(), iList.end(), 8);
if (it != iList.end())
cout << "nFound 8.n";
else
cout << "nDid not find 8.n";
return 0;
}
find() 알고리즘은 3개의 인자를 취한다. 첫번째와 두번째 인자는 검색될 범위를 나타내는
이터레이터이고, 세번째 인자는 찾을 값이다. find()가 iList.end()를 리턴했다면 값을 찾지 못하고
끝에 도달한 것이고 그렇지 않다면 그 값이 있는 곳을 가리키고 있을 것이다. 위의 결과는
아래와 같다.
Found 8.
그렇다면 검색한 값 8이 콘테이너에서 몇번째 위치에 있는지 어떻게 알수 있을까? 아마도
일치되는 아이템의 offset에서 시작 이터레이터를 빼주면 될것이라고 생각할 것이다.
(it - iList.begin()). 그러나 이것은 리스트의 이터레이터로 사용된 올바른 오퍼레이션이 아니다.
리스트 이터레이터는 단지 bidirectional 이터레이터다. 따라서 여기에 산술 오퍼레이션을
수행할 수 없고 랜덤 액세스를 지원하는 vector나 deque에서나 산술 오퍼레이션을 쓸 수 있다.
그러므로 iList가 아니라 벡터 v라면 위의 코드를 다시 써서 검색 위치를 나타낼 수 있다.
it = find (v.begin(), v.end(), 8);
if (it != v.end())
cout << "nFound 8 at location " << (it - v.begin());
else
cout << "nDid not find 8.";
위의 결과는
Found 8 at location 3
이 될 것이다.
인자로 이터레이터를 받는 다른 예제를 보자. vector를 copy() 알고리즘으로 다른 vector로 복사
하는데 인자로 복사원본의 범위와 목적 콘테이너를 적어준다.
// itercopy.cpp // uses iterators for copy() algorithm
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
int beginRange, endRange;
int arr[] = { 11,13,15,17,19,21,23,25,27,29};
vector<int> v1 (arr, arr+
10); // 벡터 초기화
vector<int> v2 (10); // 초기화되지 않은 벡터
cout << "Enter range to be copied (example: 2 5) ";
cin >> beginRange >> endRange;
vector<int>::iterator iter1 = v1.begin() + beginRange;
vector<int>::iterator iter2 = v1.begin() + endRange;
vector<int>::iterator iter3;
// copy from v1 to v2
iter3 = copy (iter1, iter2, v2.begin());
// iter3에는 마지막 아이템이 복사된다.
iter1 = v2.begin();
while (iter1 != iter3)
cout << *iter1++ << ' ';
cout << endl;
return 0;
}
범위를 입력해주면 결과는
Enter range to be copied (example: 2 5): 3 6
17 19 21
이 될 것이다. v2의 모든 아이템을 보여줄 필요없이 복사된 것만 보여주면 된다. 다행히도
copy() 알고리즘은 복사될 콘테이너의 마지막 포인터(이터레이터)를 리턴한다. 이 프로그램은
이 값을 이용하여 복사된 값들을 display하게 했다.
4.5 Speialized Iterators
이번 장에서는 특별한 이터레이터 형태 2가지를 살펴본다. 첫번째는 흥미있는 방법으로
이터레이터의 작동을 변경할 수 있는 이터레이터 어뎁터와 마치 이터레이터 처럼 동작하는
입력과 출력 스트림을 허용하는 스트림 이터레이터들이다.
4.5.1 Iterator Adapters
STL의 이터레이터는 보통 3개의 변종을 제공한다. 이런것들에는 reverse iterator,
insert iterator, raw storage iterator가 있다. reverse iterator는 콘테이너의 내용을
역순으로 참조하기위해 사용되고, insert iterator는 copy()나 merge()같은 알고리즘이 콘테이너의
내용을 변경시키고자 할 때 사용된다. 따라서 데이터가 있는 곳에 덮어쓰게 된다. 마지막으로
raw storage iterator는 초기화되지 않은 메모리로 결과를 저장하고자 할때 사용하면 된다.
그러나, raw storage iterator는 특별한 경우에만 사용된다. 그러므로 이 내용은 다루지 않을
것이다.
4.5.1.1 Reverse Iterators
콘테이너의 끝에서 부터 역순으로 반복하기를 원한다고 가정해보자.
언뜻 생각하기에 다음처럼 하고 싶을 것이다.
list<int>::iterator iter;
iter = iList.end();
while ( ier != iList.begin() )
cout << *iter-- << ' ';
하지만 불행하게도 위의 코드는 돌아가지 않는다. 이유는 단 한가지, 범위가 틀리기 때문이다.
(n-1 부터 0까지 여야 되지만 위의 코드는 n 부터 0까지 가 된다)
역순으로 반복하고 싶을때는 reverse iterator를 사용하면 된다. ITEREV 프로그램은
역순으로 데이터를 보여주기 위해 reverse iterator가 사용된 경우를 보여준다.
// iterev.cpp // demonstrates reverse iterator
#include <iostream>
#include <list>
using namespace std;
int main()
{
int arr[] = {2, 4, 6, 8, 10};
list <int> theList;
for (int j=0; j<5; j+
+)
theList.push_back( arr[j] );
list< int>::reverse_iterator revit;
revit = theList.rbegin();
while (revit != theList.rend() )
cout << *revit++ << ' ';
cout << endl;
return 0;
}
이 프로그램의 결과는
10 8 6 4 2
가 된다. reverse iterator를 사용할때 rbegin()과 rend()를 사용한다. (이걸로 정상적
순서로된 이터레이터에 적용하면 안된다. -_-;) 당황스럽겠지만 콘테이너의 끝에서부터 시작하기
위해 멤버함수는 rbegin()을 사용해야하고 이터레이터를 --하지 말고 +
+해야 한다.
reverse iterator는 항상 rbegin()으로 시작하고 rend()로 끝나고, 이터레이터를 증가 시켜야한다.
4.5.1.2 Insert Iterators
copy() 같은 일부 알고리즘들은 존재하는 내용위에 덮어쓰는 경우가 있다. COPYDEQ 프로그램은
다른 데큐로 복사하는 예를 보여준다.
// copydeq.cpp // demonstrates normal copy with deques
#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;
int main()
{
int arr1[] = {1, 3, 5, 7, 9};
int arr2[] = {2, 4, 6, 8, 10};
deque <int> d1;
deque <int> d2;
for (int j=0; j<5; j+
+) {
d1.push_back( arr1[j] );
d2.push_back( arr2[j] );
}
// d1 을 d2로 복사한다.
copy(d1.begin(), d1.end(), d2.begin());
for (int k=0; k<d2.size(); k+
+)
cout << d2[k] << ' ';
cout << endl;
return 0;
}
이 프로그램의 결과는 1 3 5 7 9 가 된다.
d2를 들여다 보면 d2가 가지고 있던 내용들이 d1의 겻들로 덮어 쓰여진 것이다.
이런 동작은 일반적인 것이다. 하지만 때로는 copy() 보다는 콘테이너에 새로운 엘리먼트를
추가하고 싶을때도 있을 것이다. 이때는 insert 이터레이터를 사용하면 된다. 이것의 세가지
변종을 보자
● back_inserter: 뒤에다 new 엘리먼트를 넣는다.
● front_inserter: 앞에다 new 엘리먼트를 넣는다.
● inserter : new 아이템을 원하는 위치에 넣는다.
DINSITER 프로그램은 back_inserter 의 사용 예를 보여준다.
// dinsiter.cpp // demonstrates insert iterators with queues
#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;
int main()
{
int arr1[] = {1, 3, 5, 7, 9};
int arr2[] = {2, 4, 6,};
deque <int> d1; deque <int> d2;
for (int i=0; i<5; i+
+)
d1.push_back( arr1[i] );
for (int j=0; j<3; j+
+)
d2.push_back( arr2[j] );
// d1을 d2로 복사하는데 순서는 역순이된다.
copy (d1.begin(), d1.end(), back_inserter(d2));
cout << "nd2: ";
for (int k=0; k<d2.size(); k+
+)
cout << d2[k] << ' ';
cout << endl;
return 0;
}
타겟 콘테이너의 뒤에다가 삽입하기 위해 back_iterator는 push_back() 멤버함수를 이용하고
소스 콘테이너의 내용은 변함이 없다. 이 프로그램의 결과는 원래 d2의 내용에 d1의 내용이
추가된
2 4 6 1 3 5 7 9
가 된다.
만일 front_inserter 를 사용한다면
copy (d1.begin(), d1.end(), front_inserter (d2));
push_front() 멤버함수를 이용하여 앞에서부터 삽입해나간다.
결과: 9 7 5 3 1 2 4 6
또한 임의의 위치에 new 아이템을 추가할 수도 있다. 아래는 d2의 처음에 d1을 추가하는 것이다.
copy (d1.begin(), d1.end(), inserter(d2, d2.begin()));
결과: 1 3 5 7 9 2 4 6
여기서 주의할 점은 inserter의 두번째 인자는 vector에서는 사용할 수 없다는 것이다.
이유는 vector에는 push_front() 메쏘드가 없기 때문이다. 백터는 무조건 끝에서부터
접근이 가능하다.
4.5.1.3 Stream Iterators
스트림 이터레이터는 화일과 I/O 디바이스(cin이나 cout)를 이터레이터로 다룰수 있는 방법을 제공
한다. 이것은 알고리즘의 인자로 파일과 IO 디바이스를 이용하여 쉽게 처리할 수 있게 해준다.
input과 output이터레이터의 주목적이 이러한 스트림 이터레이터 클래스를 지원하는것이다.
알고리즘은 직접 input과 output이터레이터를 사용할수 있게된다. 사실 스트림 이터레이터는
서로 다른 input이나 output을 템플릿화한 것이다. 여기에는 두가지의 스트림이 이터레이터가 있는데
ostream_iterator와 istream_iterator이다.
4.5.1.3.1 The ostream_iterator Class
ostream_iterator 오브젝트는 어떤 알고리즘이나 인자로 사용할 수 있다.
// outiter.cpp
// demonstrates ostream_iterator
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;
int main()
{
int arr [] = { 10, 20, 30, 40, 50 };
list <int> theList;
for (int j=0; j<5; j+
+)
theList.push_back (arr[j]);
ostream_iterator<int> ositer(cout, ","); // ostream iterator
cout << "nContents of list: ";
copy (theList.begin(), theList.end(), ositer); // display list
cout << endl;
return 0;
}
여기서는 int 값을 읽기 위해 ostream iterator를 정의했다. 생성자의 첫번째 인자는
int 값이 씌어질 stream value이고, 두번째 인자 문자열은 각 값마다 덫붙는 문자열이다.
일반적으로 stream value는 화일명이나 cout이면 된다.
copy() 알고리즘은 리스트의 내용을 cout으로 복사하게된다. copy()의 세번째 인자가 바로
ostream iterator를 나타낸다.
위의 결과는 다음과 같다.
Contents of list: 10, 20, 30, 40, 50,
다음 예제 FOUTITER는 ostream iterator로 화일을 사용하는 것을 보여준다.
// outiter.cpp
// demonstrates ostream_iterator with files
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;
int main()
{
int arr [] = { 11, 21, 31, 41, 51 };
list <int> theList;
for (int j=0; j<5; j+
+)
theList.push_back (arr[j]);
ofstream outfile ("ITER.DAT"); // create file object
ostream_iterator<int> ositer(outfile, " "); // ostream iterator
cout << "nContents of list: ";
copy (theList.begin(), theList.end(), ositer); // display list
return 0;
}
화일과 연계하기 위해서는 반드시 ofstream을 선언해야한다. 프로그램을 실행하고
메모장으로 ITER.DAT를 열어보면 다음과 같은 결과를 확인할 수 있다.
11 21 31 41 51
4.5.1.3.2 The istream_iterator Class
istream_iterator 오브젝트는 input iterator를 가진 어떤 알고리즘이든지 인자로 사용
할 수 있다. 예제 INITER는 이러한 오브젝트가 copy() 알고리즘의 처음 2개의 인자로 사용
되는 걸 보여준다.
// initer.cpp
// demonstrates istream_iterator
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
int main()
{
list<float> fList (5); // uninitialized list
cout << "nEnter 5 floating-point numbers: ";
istream_iterator <float> cin_iter(cin); // cin
istream_iterator <float> end_of_stream; // eos
//K:이곳에서 cin_iter에 값을넣고 하나씩 EOS가 될때까지 fList로 계속 복사한다.
copy ( cin_iter, end_of_stream, fList.begin() );
cout << endl;
ostream_iterator<float> ositer(cout, "--");
copy (fList.begin(), fList.end(), ositer);
cout << endl;
return 0;
}
위 프로그램의 결과는
Enter 5 floating-point numbers: 1.1 2.2 3.3 4.4 5.5
1.1--2.2--3.3--4.4--5.5--
copy()에서 주목할 사항은, 데이터를 cin에서 받기 때문에 복사될 범위의 끝(end_of_stream)을 명시
한다는 것이다. istream_iterator는 cin_iter라고 선언된 cin하고 연결된다. 그렇다면
end_of_stream은 무엇인가? 인자가 없이 생성된 end_of_stream은 이 프로그램에서 특별한
역할을 담당한다. 이것은 항상 스트림의 끝을 나타내는 istream_iterator를 생성한다.
user가 데이터를 입력하면서 어떻게 end-of-stream임을 알릴 수 있을까? 그것은 바로
<ctrl><z>를 눌러 입력을 종료시키면 된다. 그리고 위에선, 물론 다른 방법이 많겠지만,
리스트의 내용을 출력하기 위해 ostream_iterator를 사용했다.
다음 예제 FINITER는 copy()의 입력으로 cin대신 file을 사용한 것이다.
// finiter.cpp // demonstrates istream_iterator with files
#include <iostream> #include <list>
#include <fstream> #include <algorithm>
using namespace std;
int main()
{
list<int> iList(5);
ifstream infile ("ITER.DAT"); // create input file object
// (ITER.DAT는 미리 준비를 해야겠죠)
istream_iterator<int> file_iter(infile); // file
istream_iterator<int> end_of_stream; // eos
copy ( file_iter, end_of_stream, iList.begin() );
cout << endl;
// dispaly iList
ostream_iterator<int> ositer(cout, "--");
copy (iList.begin(), iList.end(), ositer);
cout << endl;
return 0;
}
FINITER의 결과는 다음과 같다.
11--21--31--41--51—
이미 존재하는 화일로써 ITER.DAT를 나타내는 ifstream을 사용했다. 위에서 cout대신에
ofstream을 이용하여 outfile로 대치해주면 결과를 화일로도 저장할 수 있다.
그리고 또다른 응용으로써 iList에 데이터를 삽입하기위해 back_inserter를 사용할 수 있다.
방법은 초기화 되지 않은 iList만들고 추가해 나가면 된다.
5 Associative Containers
이제까지 고정된 선형 sequence에 데이터를 저장하는 sequence 콘테이너(vector, list, deque)
를 다루었었다. 유용한 것들이지만, item을 찾기위해서는 콘테이너에 있는 모든 item들을
하나하나 비교해 나감으로써만 가능하다.
반면에 associative 콘테이너에서는 item이 일련의 연속으로 정렬되어 있다. 좀더 복잡한
정렬방식을 사용함으로써 찾고자 하는 item을 빠르게 검색할 수가 있다. 이러한 배열법은
hash table처럼 다른 접근법을 사용하지만, 일반적으로 tree구조를 띈다.
따라서 assoiative 콘테이너의 가장 큰 장점은 검색 속도이다.
검색은 일반적으로 숫자나 문자열같은 single value로 표현되는 key로 이루어진다.
이 키값은 콘테이너에서 object의 속성을 나타내거나, 전체 object를 나타내기도 한다.
STL에서 associative 콘테이너의 2가지 주요 분류로 set과 map이 있다.
set은 key를 포함한 object를 저장한다. map은 두부분으로(pair) 나누어 저장하는데,
첫번째 부분은 key를 포함한 object이고 두번째 부분은 value를 포함하는 object이다.
set과 map은 모두, 각 키에 대해서 오직 하나의 값만 저장하게 된다. 이것은 사전에서
각 단어에 대해서 하나이상의 엔트리를 갖지 못하도록 하는 것과 마찬가지이다.
그러나 STL은 두가지 버전을 가지고 있어서 위의 제약사항이 엄격하게 적용되지는 않는다.
multiset, multimap은 set, map과 비슷하지만 같은 키에대해서 여러개의 인스턴스를 가질수 있다.
associtave 콘테이너는 다른 콘테이너들과 많은 멤버함수들을 공유하지만,
lower_bound()나 equal_range() 같은 몇몇 알고리즘들은 associative 콘테이너에만 있다.
5.1 Sets and Multisets
set은 주로 데이터베이스에서 employees같은 사용자정의 클래스를 저장하는데 사용된다.
또한 set은 string같은 단순 object를 저장하는데도 사용된다.
그림 5.5는 set이 데이터를 저장하는 모양새를 나타낸다. object는 순서데로 정렬되어지고,
모두 key를 가진다.
set
"puma" ┌───────────────┐
────┐ │ │
│ │ ┌───────┐ │
│ │ │ "cat" │ │
│ │ └───────┘ │
│ │ │
│ │ ┌───────┐ │
│ │ │ "dog" │ │
│ │ └───────┘ │
│ │ │
│ │ ┌───────┐ │
│ │ │ "ocelot" │ │
│ │ └───────┘ │
│ │ │
│ │ ┌───────┐ │
└────┼─▶ │ "puma" │ │
│ └───────┘ │
│ │
│ ┌───────┐ │
│ │ "raindeer" │ │
│ └───────┘ │
│ ▲ │
└────┼──────────┘
│
└─ keys
[그림 5.5] A Set of string objects.
// set.cpp
// set stores string objects
#include <iostream>
#include <set>
#pragma warning (disable: 4786) // for set (microsoft compilers only)
#include <string>
using namespace std;
int main()
{
string names [] = { "Juanita", "Robert", "Marry", "Amanda", "Marie" };
set <string, less<string> > nameSet (names, names+
5);
set <string, less<string> >::iterator iter;
nameSet.insert ("Yvette"); // insert more names
nameSet.insert ("Larry");
nameSet.insert ("Robert"); // no effect; already in set
nameSet.insert ("Barry");
nameSet.erase ("Marry"); // erase a name
cout << "nSize=" << nameSet.size() << endl;
iter = nameSet.begin();
while (iter != nameSet.end())
cout << *iter+
+ << 'n';
string searchName;
cout << "Enter name to search for: ";
cin >> searchName;
iter = nameSet.find (searchName);
if ( iter == nameSet.end() )
cout << "The name " << searchName << " is NOT in the set.";
else
cout << "The name " << *iter << " IS in the set.";
cout << endl;
return 0;
}
set을 선언할 때, 저장될 object(string)의 타입을 명시하고 멤버를 정렬하기위해
function object인 less<> ()를 사용했다. 위의 코드를 보면 지금까지 보아온
STL의 다른 콘테이너들과 유사함을 알 수 있다. array를 이용해서 set을 초기화 하고
insert() 멤버함수를 이용하여 새로운 멤버를 추가하고 반복자(iterator)를 통해 출력을 한다.
set에서 특정한 값을 찾기위해서는 find() 멤버함수를 사용한다. (sequential 콘테이너에서는
find() 알고리즘을 사용한다.)
다음은 위의 프로그램을 실행시키고 "George"란 이름을 입력한 결과이다.
Size=7
Amanda
Barry
Juanita
Larry
Marie
Robert
Yvette
Enter name to search for: George
The name George is NOT in set.
그럼 이제 associative 콘테이너에만 있는 멤버함수에 대해서 살펴보도록 하자.
다음의 예제는 앞에서 말한 예제는 lower_bound()와 upper_bound()에 대한 것이다.
// setrange.cpp
// tests ranges within a set
#include <iostream>
#include <set>
#pragma warning (disable: 4786) // for set (microsoft compilers only)
#include <string>
using namespace std;
int main()
{
set <string, less<string> > organic;
set <string, less<string> >::iterator iter;
organic.insert ("Curine"); // insert organic compounds
organic.insert ("Xanthine"); organic.insert ("Curarine");
organic.insert ("Melamine"); organic.insert ("Cyanimide");
organic.insert ("Phenol"); organic.insert ("Aphrodine");
organic.insert ("Imidazole"); organic.insert ("Cinchonie");
organic.insert ("Palmitamide");
iter = organic.begin();
while ( iter != organic.end() )
cout << *iter+
+ << 'n';
string lower, upper;
cout << "nEnter range (example C Czz): ";
cin >> lower >> upper;
iter = organic.lower_bound (lower);
while ( iter != organic.upper_bound (upper) )
cout << *iter+
+ << 'n';
return 0;
}
이 프로그램은 처음에 조직 구성원을 출력한다. 그리고 프롬프트상에서 입력받은
범위에 해당하는 데이터들을 출력한다.
Aphrodine
Cinchonie
Curarine
Curine
Cyanimide
Imidazole
Melamine
Palmitamide
Phenol
Xanthine
Enter range (example C Czz): Aaa Curb
Aphrodine
Cinchonine
Curarine
lower_bound() 멤버함수는 인자로 키값을 받고 키값보다 크거가 같은 값을 갖는
처음 엔트리를 가리키는 iterator를 리턴한다.
upper_bound() 멤버함수는 역시 인자로 키값을 받고 키값보다 작거나 같은 값을
갖는 처음 엔트리를 리턴한다.
5.2 Maps and Multimaps
map은 key 오브젝트와 value오브젝트의 쌍(pair)으로 이루어진 것을 저장한다.
key 오브젝트는 검색에 사용될 key를 포함하는 오브젝트이고, value 오브젝트는
첨부된 데이터이다. key 오브젝트는 set에서 처럼 strings, numbers, 그리고
좀더 복잡한 클래스 오브젝트가 될 수 있다. value는 주로 strings 나 numbers가
되지만 이것 또한 복잡한 클래스오브젝트가 될 수 있다.
예를들어, 어떤 문서가 있을때, key가 word이고 value는 문서에서 이 단어가 쓰인
횟수로 나타낼 수 있다. 이처럼 map은 주로 빈도테이블(frequency table)을 구성하는데
사용된다. 또는 key를 단어(word)로 하고 value를 페이지 번호 리스트로 할 수도 있다.
그림 5.6은 사전처럼 key가 단어이고 value가 설명을 나타내는 것을 도식화 한것이다.
"snail" ┌─────────────────────────┐
────┐ │ │
│ │ ┌───────┐ │
│ │ │ "cat" ├ "A small, furry animal │
│ │ └───────┘ that chases mice." │
│ │ │
│ │ ┌───────┐ │
│ │ │ "dog" ├ "A largre, hairy animal │
│ │ └───────┘ that chases sticks." │
│ │ │
│ │ ┌───────┐ │
└────┼─▶ │ "snail" ├ "A small, shelled animal │
│ └───────┘ that eats gardends." │
│ │
│ ┌───────┐ │
│ │ "puma" ├ "A large, furry animal │
│ └───────┘ that eats hikers." │
│ ▲ │
└────┼────────────────────┘
│
└─ key-value pairs
[그림 5.6] A map of word-phrase pairs.
map은 Associative Array로써 사용된다. C+
+ 배열에서 특정 값을 참고하기 위해 사용되는
배열 인덱스는 integer이다. 즉 anArray[3] 처럼 사용된다. Associative Array은
배열 인덱스 타입을 맘데로 선택할 수 있는 배열 이다. 만약 인덱스 타입을 string으로
했다면 anArray ["jane"]와 같은 표현이 가능하다는 것이다.
5.2.1 An Associative Array
이제 map의 예제로 Associative Array를 사용한 것을 보자. key는 어떤 도시의 이름이 되고
value는 이 도시의 인구를 나타낸다.
// asso_arr
// demonstrates map used as associative array
#include <iostream>
#include <string>
#include <map>
#pragam warning (disable: 4786) // for map (Microsoft only)
using namespace std;
int main ()
{
string name;
int pop;
string states [] = { "Wyoming", "Colorado", "Nevada",
"Montana", "Arizona", "Idaho" };
int pops [] = {470, 2890, 800, 787, 2781, 944 };
map <string, int, less<string> > mapStates;
map <string, int, less<string> >::iterator iter;
for (int j=0; j<6; j+
+) {
name = states[j];
pop = pops[j];
mapStates[name] = pop; // map에 데이터를 추가한다.
}
cout << "Enter state: ";
cin >> name;
pop = mapStates[name]; // 인구자료를 찾는다.
cout << "Population: " << pop << ",000n";
cout << endl;
for (iter = mapStates.begin(); iter != mapStates.end(); iter+
+)
cout << (*iter).first << ' ' << (*iter).second << ",000n";
return 0;
}
프로그램을 실행시키고, 도시 이름을 입력하면 프로그램은 map에서 입력한 도시를
키로 해서 인구를 찾아 다시 화면에 출력해주고 전체 자료를 출력한다.
Enter state: Wyoming
Population: 470,000
Arizona 2718,000
Colorado 2890,000
Idaho 944,000
Montana 787,000
Nevada 800,000
Wyoming 470,000
set이나 map의 검색 속도는 뛰어나다. 이 예제로 속도를 확인하는 것은 어렵지만
수백만건의 데이터를 입력하고 시험해보면 금방 알 수 있다.
위의 예제에서 주목할 것은 map을 선언할때 세번째로 들어간 파라미터이다.
map <string, int, less<string> > mapStates;
^^^^
첫번째 인자는 key 오브젝트의 타입이고, 두번째 인자는 value 오브젝트의 타입이다.
마지막으로 세번째 인자는 키가 저장될때의 정렬방식을 지정한다.
그리고 한가지 더 주의할 것은 map에 데이터를 추가하는 부분이다.
mapStates[name] = pop;
위 표현은 C+
+ 배열 표현처럼 해당 인덱스에 데이터를 할당하는 것이지만
인덱스 타입은 string이고 배열크기가 정해지지 않았기 때문에 없으면 추가하게 된다.
반대로 값을 참조할때는 아래 처럼 한다.
pop = mapStates[name];
그리고 반복자(iterator)를 사용할 때는 (*iter).first가 key오브젝트이고
(*iter).second가 value 오브젝트이다.
6. Storing User-Defined Objects
이제까지 언급된 예제들은 기본타입으로 존재하는 오브젝트만 다루었지만 STL은
사용자가 작성한 오브젝트 클래스로도 작업할 수 있다. 이번 장에서는 사용자 정의
오브젝트에 대해서 다루어본다.
6.1 A Set of person Objects
이름, 성, 전화번호를 갖는 person 클래스로 시작해보자. 이 클래스를 set에 저장시켜
전화번호 데이터베이스를 만들것이다.
// setpers.cpp
// uses a multiset to hold person objects
#include <iostream>
#include <set>
#pragma warning (disable:4786) // for set (Microsoft only)
#include <string>
using namespace std;
class person
{
private:
string lastName;
string firstName;
long phoneNumber;
public:
person () : lastName ("blank"), firstName ("blank"), phoneNumber (0)
{ }
person (string lana, string fina, long pho) :
lastName(lana), firstName (fina), phoneNumber (pho)
{ }
friend bool operator< (const person&, const person&);
friend bool operator== (const person&, const person&);
void display () const { // display person's data
cout << endl << lastName << ",t" << firstName
<< "ttphone: " << phoneNumber;
}
};
bool operator < (const person& p1, const person& p2)
{
if (p1.lastName == p2.lastName)
return (p1.firstName < p2.firstName) ? true : false;
return (p1.lastName < p2.lastName) ? true : false;
}
bool operator== (const person& p1, const person& p2)
{
if (p1.lastName == p2.lastName && p1.firstName == p2.firstName)
return true;
else
return false;
}
//////////////////////////////////////////////////////////////////////////
int main ()
{
// person 오브젝트 생성
person pers1 ("Deauville", "William", 8435150);
person pers2 ("McDonald", "Stacey", 3327536);
person pers3 ("Bartoski", "Peter", 6946473);
person pers4 ("KuangThu", "Bruce", 4157300);
person pers5 ("Wellington", "Jhon", 9207400);
person pers6 ("McDonald", "Amanda", 8435150);
person pers7 ("Fredericks", "Roger", 7049982);
person pers8 ("McDonald", "Stacey", 7764987);
multiset < person, less<person> > persSet; // person들의 multiset
multiset < person, less<person> >::iterator iter; // iterator
// multiset에 person 오브젝트를 추가한다.
persSet.insert (pers1); persSet.insert (pers2);
persSet.insert (pers3); persSet.insert (pers4);
persSet.insert (pers5); persSet.insert (pers6);
persSet.insert (pers7); persSet.insert (pers8);
cout << "nNumber of entries = " << persSet.size();
// multiset의 content를 출력한다.
iter = persSet.begin();
while (iter != persSet.end())
(*iter+
+).display();
// lastName과 firstName을 입력받는다.
string searchLastName, searchFirstName;
cout << "nnEnter last name of person to search for: ";
cin >> searchLastName;
cout << "Enter first name: ";
cin >> searchFirstName;
// 입력받은 이름으로 searchPerson 오브젝트를 생성한다.
person searchPerson (searchLastName, searchFirstName, 0);
// 해당 이름을 가진 사람이 몇명인가 세어본다.
int cntPersons = persSet.count (searchPerson);
cout << "Number of persons with this name = " << cntPersons;
// 해당 이름을 가진 사람들을 모두 출력한다.
iter = persSet.lower_bound (searchPerson);
while ( iter != persSet.upper_bound (searchPerson) )
(*iter+
+).display ();
cout << endl;
return 0;
}
person 클래스가 STL 콘테이너로 작동되기 위해서는 몇가지 공통으로 쓰이는 멤버함수가
필요하다. 바로 기본생성자, < 오퍼레이터, == 오퍼레이터들이 필요하다. 이 멤버함수들은
리스트 클래스와 여러 알고리즘에서 유용하게 사용된다. 또한 특수한 경우를 위해
필요한 멤버함수를 정의할 수도 있다. 오퍼레이터 < 와 == 는 const 인자이어야 하고
friend로 하는게 좋다 (멤버함수로 해도 상관은 없다).
오버로드된 < 오퍼레이터는 set에 저장될때 정렬방식을 나타낸다. 예제 setpers에서는
last name을 정렬하기위해 사용되었고, last name이 동일하면 first name으로 순서를 정하게
만들었다. 위 프로그램의 결과는 아래와 같다.
Number of entries = 8
Bartoski, Peter phone: 6946473
Deauville, William phone: 8435150
Fredericks, Roger phone: 7049982
KuangThu, Bruce phone: 4157300
McDonald, Amanda phone: 8435150
McDonald, Stacey phone: 3327536
McDonald, Stacey phone: 7764987
Wellington, Jhon phone: 9207400
Enter last name of person to search for: McDonald
Enter first name: Stacey
Number of persons with name = 2
McDonald, Stacey phone: 3327536
McDonald, Stacey phone: 7764987
(K:VC++6.0에서 위의 소스는 friend함수가 클래스의 멤버변수를 사용할 수 없다는 에러가
발생하는대요 알아보니까 VC+
+의 버그라고 합니다. 해결방법은 서비스팩 5를 까시거나
.NET을 사용하시면 됩니다. SP5는 2003/4 현재 아래 주소로부터 받으실 수 있습니다.
http://www.microsoft.com/downloads/details.aspx?FamilyID=9066d31d -ba23-4e8a-b7c8-
b95f5e54f896&DisplayLang=ko#filelist)
6.2 A List of person Objects
위의 SETPERS 예제에서, set이나 multimap으로 주어진 이름을 검색하는 것은 매우 빠르다.
그렇지만, person 오브젝트를 빠르게 삽입하고 삭제하기를 고려한다면 list의 사용을 고려해야한다.
// listpers.cpp // uses a list to hold person objects
#include <iostream>
#include <list>
#include <algorithm>
#include <string>
using namespace std;
class person
{
private:
string lastName;
string firstName;
long phoneNumber;
public:
person () : lastName ("blank"),
firstName ("blank"), phoneNumber (0L)
{ }
person (string lana, string fina, long pho) :
lastName (lana), firstName (fina), phoneNumber (pho)
{ }
friend bool operator < (const person&, const person&);
friend bool operator == (const person&, const person&);
friend bool operator != (const person&, const person&);
friend bool operator > (const person&, const person&);
void display () const { // display person's data
cout << endl << lastName << ",t" << firstName
<< "ttphone: " << phoneNumber;
}
long get_phone () const {
return phoneNumber;
}
};
bool operator < (const person& p1, const person& p2)
{
if (p1.lastName == p2.lastName)
return (p1.firstName < p2.firstName) ? true : false;
return (p1.lastName < p2.lastName) ? true : false;
}
bool operator == (const person& p1, const person& p2)
{
if (p1.lastName == p2.lastName && p1.firstName == p2.firstName)
return true;
return false;
}
bool operator != (const person& p1, const person& p2)
{
return !(p1 == p2);
}
bool operator > (const person& p1, const person& p2)
{
return !(p1 < p2) && !(p1 == p2);
}
int main ()
{
list <person> persList; // list of person
list <person>::iterator iter1; // iterator to a list of persons
// put persons in list
persList.push_back (person("Deauville", "William", 8435150));
persList.push_back (person("McDonald", "Stacey", 3327536));
persList.push_back (person("Bartoski", "Peter", 6946473));
persList.push_back (person("KuangThu", "Bruce", 4157300));
persList.push_back (person("Wellington", "Jhon", 9207400));
persList.push_back (person("McDonald", "Amanda", 8435150));
persList.push_back (person("Fredericks", "Roger", 7049982));
persList.push_back (person("McDonald", "Stacey", 7764987));
cout << "nNumber of entries = " << persList.size();
iter1 = persList.begin (); // display contents of list
while ( iter1 != persList.end() )
(*iter1++).display();
// find person or person with specified name (last and first)
string searchLastName, searchFirstName;
cout << "nnEnter last name of person to search for: ";
cin >> searchLastName;
cout << "Enter first name: ";
cin >> searchFirstName;
// make a person with that name
person searchPerson (searchLastName, searchFirstName, 0L);
// search for first match of names
iter1 = find (persList.begin(), persList.end(), searchPerson);
// find additional matches
if ( iter1 != persList.end() ) {
cout << "Person(s) with that name is(are)";
do {
(*iter1).display (); // display match
+
+iter1; // search again, one past match
iter1 = find (iter1, persList.end(), searchPerson);
} while (iter1 != persList.end());
}
else
cout << "There is no person with that name.";
// find person or persons with specified phone number
cout << "nnEnter phone number (format 12345678): ";
long sNumber; // get search number
cin >> sNumber;
// iterate through list
bool found_one = false;
for (iter1=persList.begin(); iter1!=persList.end(); iter1++) {
if (sNumber == (*iter1).get_phone()) { // compare numbers
if (!found_one) {
cout << "Person(s) with that phone number is(are)";
found_one = true;
}
(*iter1).display();
}
}
if (!found_one)
cout << "There is no person with that phone number";
cout << endl;
return 0;
}
//////////////////////////////////////////////////////////////////////////
이름으로 검색할때:
위의 예제는 list를 사용하기때문에 lower_bound()/upper_bound() 멤버함수를
사용할 수가 없다. 대신에 검색을 하기위해 find() 알고리즘을 이용하여 list의
모든 멤버를 비교하게된다. 우선 최초검색을 하고, 발견되면 그 다음 위치부터
루프를 돌며 계속 비교를 하게 된다. 즉 find()를 두번 써야한다.
Phone Number로 검색할때:
find()는 검색하기위해 1차적 검색 특성(primary search characteristic)을 이용하기 때문에
이름으로 검색하는 것보다는 어렵다. 예제에서 전화번호를 찾기위해 brute force 기법을
사용하였다. 이 기법은 리스트 전체를 순회하면서 찾고자 하는 항목을 수동적으로
비교해나간다.
if ( sNumber == (*iter).get_phone() )
...
프로그램은 먼저 모든 엔트리를 출력하고나서 검색할 이름을 묻는다. 입력받은 이름을
찾아서 출력해주고, 다음과정으로 검색할 전화번호를 묻는다. 그리고 다시한번 검색을
하게된다. 결과는 다음과 같다.
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc
STL.doc

More Related Content

Similar to STL.doc

[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1
[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1
[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1
Seok-joon Yun
 
ES6 for Node.js Study 2주차
ES6 for Node.js Study 2주차ES6 for Node.js Study 2주차
ES6 for Node.js Study 2주차
승빈이네 공작소
 
R 프로그램의 이해와 활용 v1.1
R 프로그램의 이해와 활용 v1.1R 프로그램의 이해와 활용 v1.1
R 프로그램의 이해와 활용 v1.1
happychallenge
 
1.7 튜닝의도구 sql autorace
1.7 튜닝의도구 sql autorace1.7 튜닝의도구 sql autorace
Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracleBind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
엑셈
 
Java advancd ed10
Java advancd ed10Java advancd ed10
Java advancd ed10
hungrok
 
Json view 예제 설명
Json view 예제 설명Json view 예제 설명
Json view 예제 설명
Hyung Eun Jin
 
Amazon aurora 2
Amazon aurora 2Amazon aurora 2
Amazon aurora 2
EXEM
 
Windows via c++ chapter6
Windows via c++   chapter6Windows via c++   chapter6
Windows via c++ chapter6
Shin heemin
 
[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산
[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산
[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산
S.O.P.T - Shout Our Passion Together
 
C++ stl
C++ stlC++ stl
C++ stl
은아 정
 
Fundamentals of Oracle SQL
Fundamentals of Oracle SQLFundamentals of Oracle SQL
Fundamentals of Oracle SQL
JAEGEUN YU
 
Memcached의 확장성 개선
Memcached의 확장성 개선Memcached의 확장성 개선
Memcached의 확장성 개선NAVER D2
 
PL/SQL - 10g Release1
PL/SQL - 10g Release1PL/SQL - 10g Release1
PL/SQL - 10g Release1
Michael/Taewoo Kim
 
2014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #72014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #7
Chris Ohk
 
02. data structure and stl
02. data structure and stl02. data structure and stl
02. data structure and stl
승혁 조
 
C++ Advanced 강의 4주차
 C++ Advanced 강의 4주차 C++ Advanced 강의 4주차
C++ Advanced 강의 4주차
HyunJoon Park
 
Java stream v0.1
Java stream v0.1Java stream v0.1
Java stream v0.1
Hyosang Hong
 
Java stream v0.1
Java stream v0.1Java stream v0.1
Java stream v0.1
Hyosang Hong
 

Similar to STL.doc (20)

[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1
[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1
[2015-06-12] Oracle 성능 최적화 및 품질 고도화 1
 
강의자료4
강의자료4강의자료4
강의자료4
 
ES6 for Node.js Study 2주차
ES6 for Node.js Study 2주차ES6 for Node.js Study 2주차
ES6 for Node.js Study 2주차
 
R 프로그램의 이해와 활용 v1.1
R 프로그램의 이해와 활용 v1.1R 프로그램의 이해와 활용 v1.1
R 프로그램의 이해와 활용 v1.1
 
1.7 튜닝의도구 sql autorace
1.7 튜닝의도구 sql autorace1.7 튜닝의도구 sql autorace
1.7 튜닝의도구 sql autorace
 
Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracleBind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
 
Java advancd ed10
Java advancd ed10Java advancd ed10
Java advancd ed10
 
Json view 예제 설명
Json view 예제 설명Json view 예제 설명
Json view 예제 설명
 
Amazon aurora 2
Amazon aurora 2Amazon aurora 2
Amazon aurora 2
 
Windows via c++ chapter6
Windows via c++   chapter6Windows via c++   chapter6
Windows via c++ chapter6
 
[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산
[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산
[SOPT] 데이터 구조 및 알고리즘 스터디 - #02 : 스택, 큐, 수식 연산
 
C++ stl
C++ stlC++ stl
C++ stl
 
Fundamentals of Oracle SQL
Fundamentals of Oracle SQLFundamentals of Oracle SQL
Fundamentals of Oracle SQL
 
Memcached의 확장성 개선
Memcached의 확장성 개선Memcached의 확장성 개선
Memcached의 확장성 개선
 
PL/SQL - 10g Release1
PL/SQL - 10g Release1PL/SQL - 10g Release1
PL/SQL - 10g Release1
 
2014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #72014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #7
 
02. data structure and stl
02. data structure and stl02. data structure and stl
02. data structure and stl
 
C++ Advanced 강의 4주차
 C++ Advanced 강의 4주차 C++ Advanced 강의 4주차
C++ Advanced 강의 4주차
 
Java stream v0.1
Java stream v0.1Java stream v0.1
Java stream v0.1
 
Java stream v0.1
Java stream v0.1Java stream v0.1
Java stream v0.1
 

More from Daniel Shin

<마블 프로젝트> 소설, 시나리오, 만화, 애니메이션. 인문학 육성사업
<마블 프로젝트>  소설, 시나리오, 만화, 애니메이션. 인문학 육성사업<마블 프로젝트>  소설, 시나리오, 만화, 애니메이션. 인문학 육성사업
<마블 프로젝트> 소설, 시나리오, 만화, 애니메이션. 인문학 육성사업
Daniel Shin
 
인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태
인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태
인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태
Daniel Shin
 
0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시
0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시
0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시
Daniel Shin
 
게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료
게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료
게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료
Daniel Shin
 
리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일
리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일
리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일
Daniel Shin
 
얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성
얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성
얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성
Daniel Shin
 
배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행
배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행
배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행
Daniel Shin
 
뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인
뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인
뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인
Daniel Shin
 
3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc
3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc
3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc
Daniel Shin
 
인터넷 오락실게임 사업계획서_20230320v2.doc
인터넷 오락실게임 사업계획서_20230320v2.doc인터넷 오락실게임 사업계획서_20230320v2.doc
인터넷 오락실게임 사업계획서_20230320v2.doc
Daniel Shin
 
덴티스 면접 포트폴리오_신동인v1.docx
덴티스 면접 포트폴리오_신동인v1.docx덴티스 면접 포트폴리오_신동인v1.docx
덴티스 면접 포트폴리오_신동인v1.docx
Daniel Shin
 
C언어강의 발표자료 1강.pptx
C언어강의 발표자료 1강.pptxC언어강의 발표자료 1강.pptx
C언어강의 발표자료 1강.pptx
Daniel Shin
 
포인터와 참조_20220908v2_신동인.pptx
포인터와 참조_20220908v2_신동인.pptx포인터와 참조_20220908v2_신동인.pptx
포인터와 참조_20220908v2_신동인.pptx
Daniel Shin
 
resume20220510v3.pptx
resume20220510v3.pptxresume20220510v3.pptx
resume20220510v3.pptx
Daniel Shin
 
미니메타버스v5.pptx
미니메타버스v5.pptx미니메타버스v5.pptx
미니메타버스v5.pptx
Daniel Shin
 
카툰월드기획서.pptx
카툰월드기획서.pptx카툰월드기획서.pptx
카툰월드기획서.pptx
Daniel Shin
 
프로젝트_성공하는_법.pptx
프로젝트_성공하는_법.pptx프로젝트_성공하는_법.pptx
프로젝트_성공하는_법.pptx
Daniel Shin
 
3D카툰메이커 완료세미나(복구됨)
3D카툰메이커 완료세미나(복구됨)3D카툰메이커 완료세미나(복구됨)
3D카툰메이커 완료세미나(복구됨)
Daniel Shin
 
3D 기술 세미나2주차
3D 기술 세미나2주차3D 기술 세미나2주차
3D 기술 세미나2주차
Daniel Shin
 
백업을 위한 USB운영체제 완료세미나
백업을 위한 USB운영체제 완료세미나백업을 위한 USB운영체제 완료세미나
백업을 위한 USB운영체제 완료세미나
Daniel Shin
 

More from Daniel Shin (20)

<마블 프로젝트> 소설, 시나리오, 만화, 애니메이션. 인문학 육성사업
<마블 프로젝트>  소설, 시나리오, 만화, 애니메이션. 인문학 육성사업<마블 프로젝트>  소설, 시나리오, 만화, 애니메이션. 인문학 육성사업
<마블 프로젝트> 소설, 시나리오, 만화, 애니메이션. 인문학 육성사업
 
인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태
인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태
인공지능발표-근태.ppt 유전자 알고리즘을 이용한 영상 특징 추출 경북대학교 박근태
 
0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시
0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시
0_소공 디자인.pdf ATM디자인 설계 문서 경북대학교 2024년 2월 20일 게시
 
게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료
게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료
게임 프로그래밍의 이해-신동인 2024년2월20일 게시 레볼루션 발표자료
 
리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일
리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일
리얼 연예 시뮬레이션 기획서 업무추진계획서 윤주용 길태욱 신동인 2011년 4월 7일
 
얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성
얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성
얌미르2 게임기획서.doc 이왕희 레볼루션 2024년 1월 26일 발행 미완성
 
배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행
배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행
배틀체스GO 기획서 초안 20220616v2 원작자: 신동인 2024년1월26일 발행
 
뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인
뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인
뚝딱한국요리 화면설계 2024년 1월 26일 발행 원작자: 김덕호, 신동인
 
3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc
3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc
3D창작동화전집 디지털컨텐츠 사업계획서 20230404v2.doc
 
인터넷 오락실게임 사업계획서_20230320v2.doc
인터넷 오락실게임 사업계획서_20230320v2.doc인터넷 오락실게임 사업계획서_20230320v2.doc
인터넷 오락실게임 사업계획서_20230320v2.doc
 
덴티스 면접 포트폴리오_신동인v1.docx
덴티스 면접 포트폴리오_신동인v1.docx덴티스 면접 포트폴리오_신동인v1.docx
덴티스 면접 포트폴리오_신동인v1.docx
 
C언어강의 발표자료 1강.pptx
C언어강의 발표자료 1강.pptxC언어강의 발표자료 1강.pptx
C언어강의 발표자료 1강.pptx
 
포인터와 참조_20220908v2_신동인.pptx
포인터와 참조_20220908v2_신동인.pptx포인터와 참조_20220908v2_신동인.pptx
포인터와 참조_20220908v2_신동인.pptx
 
resume20220510v3.pptx
resume20220510v3.pptxresume20220510v3.pptx
resume20220510v3.pptx
 
미니메타버스v5.pptx
미니메타버스v5.pptx미니메타버스v5.pptx
미니메타버스v5.pptx
 
카툰월드기획서.pptx
카툰월드기획서.pptx카툰월드기획서.pptx
카툰월드기획서.pptx
 
프로젝트_성공하는_법.pptx
프로젝트_성공하는_법.pptx프로젝트_성공하는_법.pptx
프로젝트_성공하는_법.pptx
 
3D카툰메이커 완료세미나(복구됨)
3D카툰메이커 완료세미나(복구됨)3D카툰메이커 완료세미나(복구됨)
3D카툰메이커 완료세미나(복구됨)
 
3D 기술 세미나2주차
3D 기술 세미나2주차3D 기술 세미나2주차
3D 기술 세미나2주차
 
백업을 위한 USB운영체제 완료세미나
백업을 위한 USB운영체제 완료세미나백업을 위한 USB운영체제 완료세미나
백업을 위한 USB운영체제 완료세미나
 

STL.doc

  • 1. -------------------------------------------------------------------------------- STL (Standard Templete Library) -form www.devpia.com VC+ + lecture 번역 : Yonghan, Yoon (yonghany@orgio.net) 편집 : Insik, You (kaerun@hanmail.net)  주석(K: ) -------------------------------------------------------------------------------- THE WAITE GROUP"s Object-Oriented Programming in C+ + Third Edition Robert Lafore(Author) SAMS(Publisher) 란 책에서 STL부분만 번역한 겁니다. -------------------------------------------------------------------------------- 0. 개요 STL에서 중요한 세가지 엔티티는 containers, algorithms, iteraters이다. containers: 메모리에 구조화된 데이터가 저장되는 방법을 말한다. template class로 구현되어 있기 때문에 다른 타입의 데이터를 저장할 수 있게 쉽게 커스터마이징 할 수있다. algorithms: -containers가 가진 데이터를 다양한 방법으로 처리하는 프로시져다. (예, sort, copy, search,merge) - template class로 구현됨(하지만 container의멤버는 아님) iterators: - 포인터 개념을 일반화 시킨것. - 콘테이너의 데이터를 가리킨다. - 포인터를 증가시키듯이 iterator를 증가시킬 수 있다. 따라서 iterator는 콘테이너의 다음 데이터를 가리키게 된다. - iterator는 콘테이너를 가지고 알고리즘을 연결하기 때문에 STL에서 핵심 부분이다. 1.콘테이너 콘테이너는 클래스 오브젝트나 int, float같은 built-in 데이터들을 저장하는 방법을 말한다. STL은 7가지 basic type이 사용가능할 뿐만 아니라, basic type에서 상속된 3가지 타입이 더 있다. 그리고 basic type에서 상속 받는 자신만의 콘테이너를 만들수도 있다. 모든 데이터를 저장할때 왜 C+ + 배열을 사용하지 않는 거쥐? 이쯤에서 왜 그렇게 많은 콘테이너가 필요한지 궁금할 것이다. 배열은 다루기 힘들고 어떤 상황에선 느리다.
  • 2. STL에서 콘테이너는 sequence, associative의 2가지 메인 카테고리로 요약된다. sequence 콘테이너는 vector,list,deque로 분류되고 associative 콘테이너는 set, multiset,map, multimap으로 분류된다. 여기에 추가로 sequence 콘테이너를 상속받은 몇개의 특수한 콘테이너들이 있다. (stack, queue,priority_queue) 그럼 다음에 차례로 이 카테고리들에 대해서 살펴보기로 하자. 1.1 sequence containers sequence 콘테이너는 거리에 늘어선 집들처럼 line으로 시각화 할수 있는 요소들의 집합을 저장한다. 각 요소들은 선을 따라서 다른 요소들과 연결되어 있다. 끝요소를 제외한 각 요소들은 어떤 요소들의 앞에 있을 수 있고 뒤에 있을 수도 있다. 일반적인 C+ + 배열이 sequence 콘테이너의 예라 할 수 있다. C+ + 배열의 한가지 문제점은 compile-time에 반드시 크기를 결정해야 한다는 것이다. 그러나 일반적으로 배열에 얼마나 많은 데이터가 저장될지는 알 수 없는 일이다. 그래서 데이터의 최대갯수를 추측하여 충분히 크게 설정하게 된다. 결과적으로 프로그램이 실행되었을 때 사용되지 않는 배열 공간이 낭비되고 또는 실행 공간 부족 에러를 도출해 낸다(심지어는 프로그램이 멈춰버리기 까지 한다). vector 콘테이너는 이러한 복잡성을 피할 수 있는 길을 제공한다. 배열의 또다른 문제점은 employee 레코드를 저장하고 이를 employee이 이름으로 정렬 하라고 한다면 이름이 L로 시작하는 employee를 삽입하려 할때 M부터 Z까지 모든 employee 레코드를 이동시켜야만 한다. 이 작업은 많은 시간이 소요될 것이다. STL은 이 문제를 풀기 위해 링크드 리스트 개념에 기초한 list 콘테이너를 제공한다. 세번째 sequence container는 stack과 queue를 조합한 것이라 생각할 수 있는 deque 이다. stack은 last-in-first-out 개념이고, queue는 first-in-first-out개념이다. 따라서 deque는 양끝에서 데이터를 추가 삭제를 할 수 있다. deque라는 단어는 Double-Ended-QUEeue를 조합한 것이다. 융통성 있는 메카니즘으로써 deque자체로도 유용할뿐만 아니라 stack과 queue로도 사용할 수 있다.
  • 3. 테이블 1.1 Basic sequence Container ================================================================================ 특징 장단점 ================================================================================ 일반 C+ + 배열 고정크기 인덱스 번호를 통한 빠른 액세스 중간값의 추가 삭제 느림. run-time시에 크기를 변경할 수 없다. -------------------------------------------------------------------------------- vector 재배치,확장배열 인덱스 번호를 통한 빠른 액세스 중간값 추가 삭제 느림 끝에 값 추가 삭제는 빠름. -------------------------------------------------------------------------------- list Doubly linked list 어떤 위치에서건 추가 삭제 빠름. 양끝에서부터 액세스 빠름. 임의 접근 느림 -------------------------------------------------------------------------------- deque vector와 유사 , 빠른 임의접근(인덱스번호 이용) 그러나 양끝에서 중간값 추가삭제 느림 액세스 가능 양끝에서 빠른 추가삭제(push & pop) ================================================================================ STL을 생성하기는 쉽지만 반드시 헤더를 include해야 한다. 예) vector< int> aVect; // ints 벡터 생성 (K:ints = integer) 또는 list< airtime> depature_list; // airtime 리스트 생성 예제에서 주목할 것은 STL 콘테이너의 크기를 지정하지 않는 다는 것이다. 콘테이너는 스스로 메모리를 할당하게 된다.
  • 4. 1.2 Associate Containers associate 콘테이너는 sequential이 아니다. 대신에 키를 이용하여 데이터를 액세스한다. 어떤 저장된 요소들을 특별한 순서로 정렬하기 위해 콘테이너가 자동으로 키를 사용한다. 이것은 알파벳 순으로 정렬된 단어를 찾아보는 보통 영어 사전과 같다. 키값을 주면 콘테이너는 이 값을 요소의 메모리 위치로 변환한다. 키만 알고 있다면 신속하게 연관된 값을 알 수 있다. STL에는 set과 map의 두 종류의 associative 콘테이너가 있다. 이것들은 모두 빠른 검색,추가,삭제를 제공하는 트리 구조로 데이터를 저장한다. set과 map은 애플리케이션의 다양한 데이터를 저장하는데 적합한 매우 융통성 있는 데이터 구조이다. 그러나 정렬과 임의접근에 필요한 다른 오퍼레이션은 비효율적이다. set은 map보다 쉽고 주로 이용된다. set은 키를 포함한 아이템 갯수를 저장한다. 키는 아이템을 정렬하기 위한 속성이다. 예를 들면, set으로 person오브젝트를 저장할때 이름속성을 키값으로 정렬해서 저장할 수 있다. 이런 경우에 이름으로 검색을 해서 빠르게 위치를 알 수 있다. int같은 기본 타입을 저장한다면, 키는 int 같은 기본 타입을 통째로 저장된다. 때론 키값으로 set에 저장된 전체 오브젝트를 참조한다. 그러나 이것은 데이터를 정렬하기 위해 사용된 the 속성을 강조하기 위한 키 오브젝트라 부르지 않는다. 키는 전체 아이템이 아니다. map은 키(key)와 값(value) 오브젝트 쌍을 저장한다. map은 종종 배열처럼 사용되지만 배열의 인덱스로 엘리먼트를 참조하지 않고, 임의 타입을 인덱스로 사용한다. 다시말해서 키 오브젝트가 인덱스 역할을 하고 value 오브젝트가 인덱스가 가리키는 값이 된다. set과 map 콘테이너는 저장된 각 데이터에 대해서 오직 하나의 키만을 제공한다. 반면에 multimap과 multiset 콘테이너는 mulitple key를 제공한다. Table 1.2 Basic Associative Containers ================================================================================ 특징 ================================================================================ set 키 오브젝트만 저장하고 각 value에 대해서 하나의 키만 제공. -------------------------------------------------------------------------------- multiset 키 오브젝트만 저장하지만 복수개의 키를 제공. -------------------------------------------------------------------------------- map value와 키를 연관시킴. 각 value에 대해 하나의 키만 제공. -------------------------------------------------------------------------------- multimap value와 키를 연관시킴. 복수개의 키를 제공. ================================================================================
  • 5. associative 콘테이너의 생성은 sequential 콘테이너처럼 하면된다. set< int> intSet; // ints의 set을 생성 또는 multiset< employee> machinists; // employee의 multiset을 생성 1.3 Member Functions 알고리즘은 sorting이나 searhing 같은 복잡한 오퍼레이션의 결과를 도출하기 위해 STL에서 가장 많이 사용되지만 콘테이너도 간단하게 특별한 타입의 작업을 수행하기 위해서 멤버 함수가 필요하다. 테이블 1.3은 대부분의 콘테이너에 공통으로 사용되는 멤버함수를 나타낸 것이다. Table 1.3 Some Member Functions Common All Containers ================================================================================ 이름 목적 ================================================================================ size() 콘테이너에 있는 아이템의 갯수를 리턴 -------------------------------------------------------------------------------- empty() 콘테이너가 empty면 true를 리턴 -------------------------------------------------------------------------------- max_size() 콘테이너가 확장 가능한 최대 크기 리턴 -------------------------------------------------------------------------------- begin() 콘테이너의 시작점을 iterator로 리턴, forward iteration -------------------------------------------------------------------------------- end() 콘테이너의 끝점을 iterator로 리턴, end forward iteration -------------------------------------------------------------------------------- rbegin( ) backword iteration을 위해 역으로 시작하는 iterator를 리턴 -------------------------------------------------------------------------------- rend() rbegin()의 반대 끝을 iterator로 리턴 ================================================================================ 이 밖에도 각각 콘테이너들이 멤버 함수들을 가지고 있지만 뒤로 미루도록 한다.
  • 6. 1.4 Container Adapters container adapter라 불리는 앞에서 언급한 일반적인 콘테이너로 부터 특별한 목적을 갖는 콘테이너를 만드는 것도 가능하다. 이러한 특별한 목적을 가지는 콘테이너들은 일반적인 콘테이너들 보다도 더 간단한 인터페이스를 가지게 된다. STL에서 콘테이너 어댑터로 구현된 특별화된 콘테이너로는 stacks, queues, 그리고 priority queues등이 있다. 우리에게 친숙한 스택은 스택의 top에서 혹은 bottom에서 pushing과 popping이 제한된 액세스로 구성된다. 큐에선 한쪽 끝에서 아이템을 push하고 다른 한쪽에서 그 아이템을 pop하게 되어 있다. 그리고 우선순위 큐(priority queue) 에서는 임의 순서로 전방에서 아이템을 push 가능하지만 다른 한쪽에서 그 아이템을 pop할 때는 항상 가장 큰 아이템 부터 pop하게 된다(우선순위 큐는 자동으로 정렬을 한다). deque가 자주 사용되긴 하지만 스택, 큐, 우선순위 큐는 서로 다른 sequence 콘테이너로부터 생성된다. 테이블 1.4는 추상 데이터 타입(abstract data type)과 구현에 사용된 시퀀스 콘테이너들을 보여준다. Table 1.4 Adapter-Based Containers ================================================================================ Container Implementation Characteristics ================================================================================ stack vector, list, deque로 구현 한쪽 끝에서만 push/pop 가능 -------------------------------------------------------------------------------- queue list, deque로 구현 한쪽에서 push, 다른쪽에서 pop -------------------------------------------------------------------------------- priority_queue vector나 deque로 구현 한쪽에서 임의 순서로 push, 다른 쪽에서 저장된 순서로 pop ================================================================================ 중첩된 템플릿 선언으로 클래스를 만드는 것도 가능하다. 예를 들어 ints 오브젝트를 갖는 deque클래스로 스택을 만든다고 할때, stack < deque< int>> aStack; 가 가능하다. 위 선언을 할 때 주의할 것은 ">>" 사용에 유의해야 한다는 것이다. stack < deque< int>> aStack; // syntaxerror 처럼 선언하면 syntax error가 발생할 것이다. 이유는 컴파일러가 >> 를 shift 연산자로 파싱하기 때문이다. 반대의 경우인 "< < " 의 사용도 마찬가지 이다.
  • 7. 1.5 Algorithms 알고리즘은 콘테이너(또는 콘테이너들)에 있는 데이터를 가지고 뭔가를 하는 함수들이다. 이미 알고 있듯이 STL에서 알고리즘은 콘테이너 클래스의 멤버 함수도 아니고 프랜드 함수도 아니다. 그렇지만 이것 또한 템플릿 함수들이다. 따라서 C+ + buit-in 배열을 사용 할수 도 있고 사용자가 만든 콘테이너 클래스를 사용할 수도 있다. 테이블 1.5는 몇가지 알고리즘들을 나타낸다. Table 1.5 Some Typical Algorithms ================================================================================ Algorithm Purpose ================================================================================ find 어떤 값과 동일한 최초의 값을 리턴 -------------------------------------------------------------------------------- count 엘리먼트의 갯수를 리턴 -------------------------------------------------------------------------------- equal 두 콘테이너의 데이터가 완전히 같으면 true를 리턴 -------------------------------------------------------------------------------- search 다른 콘테이너에서 같은 시퀀스 value의 시퀀스를 찾는다. -------------------------------------------------------------------------------- copy 시퀀스 값을 복사 -------------------------------------------------------------------------------- swap 값을 서로 바꾼다. -------------------------------------------------------------------------------- iter_swap 시퀀스 값을 서로 바꾼다. -------------------------------------------------------------------------------- fill 시퀀스의 특정 위치에 값을 채운다. -------------------------------------------------------------------------------- sort 지정한 순서로 정렬을 한다. -------------------------------------------------------------------------------- merge 두개의 저장소를 결합한다. -------------------------------------------------------------------------------- accumulate 주어진 범위의 값을 더한다. -------------------------------------------------------------------------------- for_each 각 콘테이너의 엘리먼트 값을 탐색 ================================================================================
  • 8. 다음의 데이터를 가지는 int 타입의 배열을 생성했다고 가정해보자 int arr[8] ={ 42, 31, 7, 80, 2, 26, 19, 75}; STL의 sort 알고리즘을 써서 다음처럼 정렬할 수 있다. sort (arr, arr+ 8); 위에서 arr은 arr의 시작 주소이고 arr+ 8은 끝 주소이다. iterator는 포인터 비슷하게 콘테이너에 있는 아이템(엘리먼트)을 액세스하는 엔티티이다. 주로 콘테이너에서 반복(iterating)에 의해 엘리먼트에서 엘리먼트로 선형 이동하는데 사용된다. + +오퍼레이터를 사용해서 이터레이터를 증가하면 이터레이터는 다음 엘리먼트를 가리키게 된다. 그리고 역참조(dereference)연산자 *로 이터레이터가 가리키고 있는 데이터를 참조하게 된다. STL에서 이터레이터는 iterator 클래스로 표현된다. 콘테이너에 따라서 서로다른 이터레이터를 사용해야며 이터레이터의 주요 3 가지 종류는 forward, bi-directional, random access 이다. forward 이터레이터는 콘테이너상에서 한번에 한아이템씩 전방 이동 밖에 할수 없다. 즉, + +오퍼레이터만 있으면 된다. 절대 후방으로 이동할 수 없고 콘테이너 중간위치로의 임의 설정을 할 수 없다. bidirectional 이터레이터는 전방/후방으로 모두 이동가능하다. 따라서 + +오퍼레이터와 --오퍼레이터가 모두 정의되어 있다. random access 이터레이터는 전방/후방 이동뿐만 아니라 임의 위치로 점프할 수 있다. 1.6 Iterator Characteristics ================================================================================ Read/Write 저장가능 방향 Access ================================================================================ Random Access R/W Yes 전방/후방 Random -------------------------------------------------------------------------------- Bidirectional R/W Yes 전방/후방 Linear -------------------------------------------------------------------------------- Forward R/W Yes 전방 Linear -------------------------------------------------------------------------------- Output W No 전방 Linear -------------------------------------------------------------------------------- Input R No 전방 Linear ================================================================================
  • 9. 1.7 STL의 잠재적 문제(PotentialProblems with STL) STL의 복잡성으로 인해서 컴파일러에게 무리를 주어 (K:원문이 이렇게 써있는대 아마도 잘못 쓴거 같음) 먼저, 헤더화일에 포함되어 있어서 에러 찾기가 매우 어렵다. 해결책은 유저 코드의 매 라인에 주석을 달아 놓는 길뿐이다. 프리컴파일드 헤더의 사용으로 컴파일 속도가 엄청나게 빨라지지만 STL을 사용하면 컴파일 시간을 느려지게 한다. STL은 아마도 유효한 숫자가 소실 된다고 경고를 내지만 이 경고는 무시하던지 disable 시켜버리면 된다. 2 Algorithms STL알고리즘은 데이터 콜렉션상에서 어떤 오퍼레이션을 수행한다. 이러한 알고리즘들은 STL콘테이너와 동작하도록 디자인 되어있지만 보통 C+ + 배열에도 적용 시킬 수 있다. 배열 프로그래밍을 할때 상당부분의 노가다를 줄일 수 있다. 2.1 The find()Algorithm find() 알고리즘은 어떤 값들을 가진 콘테이너에서 일치하는 첫번째 엘리먼트를 찾는데 사용한다. FIND 예제 프로그램은 ints배열에서 어떻게 찾는지를 보여준다. // find.cpp // finds the first object with a specified value #include< iostream> #include< algorithm> // forfind () using namespace std; int arr[] ={ 11, 22, 33, 44, 55, 66, 77, 88}; int main () { int *ptr; ptr = find (arr, arr+ 8, 33); // 맨 처음 나타나는 33을 찾는다. cout << “33을 가지는 첫번째 오브젝트가 발견된 위치는” << (ptr-arr) << endl; return 0; } (K:VC+ +에서 한다면 return 0; 바로전에 getchar(); 같은걸 써주거나 브래이크 포인트를 찍어야함.) 결과 33을 가지는 첫번째 오브젝트가 발견된 위치는 2
  • 10. 이 프로그램에서 algorithm 화일을 인클루드했는데, 여기서 주의할것은 STL에서 헤더화일들은 확장자(.H나 .CP같은P)를 갖지 않는다는 것이다. find()함수에서 처음 두개의 파라미터는 범위를 가리킨다. 위의 예에서 C+ + 포인터를 사용하였는데 이런 경우는 이터레이터에서 특별한 경우이다. 첫번째 파라미터는 첫번째 값을 가리키는 이터레이터(포인터)이고 두번째 파라미터는 마지막 엘리먼트를 말한다. 위 예제를 C+ +의 for 루프로 다시 표현하면 for (int j=0; j< 8; j+ +) // from 0to 7 { if (arr[j] == 33) { cout << “33을 가지는 첫번째 오브젝트가 발견된 위치는” << j << endl; break; } } FIND 예제의 find()는 for 루프를 작성하는 불편함을 없애준다. 좀더 복잡한 상황에서 알고리즘은 아주 복잡한 코드를 작성하는 일을 피하게 해준다. 2.2 The count()Algorithm count()는 콘테이너에서 특정한 값을 가지는 엘리먼트가 몇개 있는지 알아본다. COUNT 예제를 보자. //count.cpp // countthe number of objects with a specified value. #include< iostream> #include< algorithm> // forcount() using namespace std; int arr[] ={ 33, 22, 33, 44, 33, 66, 77, 88}; int main() { int n =count (arr, arr+ 8, 33); // 33의 갯수를 센다. cout << “33이 ” << n << “개 있습니다.” << endl; return 0; }
  • 11. 결과: 33이 3개 있습니다. 2.3 The sort() Algorithm 아마 sort() 알고리즘은 짐작이 갈것이다. 바로 SORT 예제를 보자. // sort.cpp // integers 배열을 정렬한다. #include< iostream> #include< algorithm> using namespace std; int arr[] ={ 45, 2, 22, -17, 0, -30, 25, 55}; int main() { sort (arr, arr+ 8); for (int j=0; j< 8; j+ +) cout << arr[j] << " "; return 0; } 결과: -30 -17 0 2 22 25 45 55 2.4 The search() Algorithm 어떤 알고리즘은 한번에 두개의 콘테이너로 작업한다. find()알고리즘은 싱글 콘테이너에서 찾아보는 것이지만, search()알고리즘은 어떤 한 콘테이너의 연속되는 값들(패턴)을 다른 한 콘테이너에서 찾아본다. // search.cpp #include< iostream> #include< algorithm> using namespace std; int source [] = {11, 44, 33, 11, 22, 33, 11, 22, 44}; int pattern[] ={11, 22, 33};
  • 12. int main () { int *ptr; ptr = search(source,source+ 9, pattern,pattern+ 3); if (ptr == source+ 9) cout << “No match foundn”; else cout << “Match at ” << (ptr-source) << endl; return 0; } 결과: Match at 3 이터레이터 값 ptr이 source의 끝까지 갔다면 찾지 못했다는 소리이다. 2.5 The merge() Algorithm 3개의 콘테너를 가지고 작업을 하는 알고리즘으로써 2개의 소스 콘테이너를 합하여 목적 콘테이너에 넣는다. // merge.cpp #include< iostream> #include< algorithm> using namespace std; int src1[] = { 2, 3, 4, 6, 9 }; int src2[] = { 1, 3, 5 }; int dest[8]; int main() { // src1과 src2를 dest로 merge한다. merge (src1, src1+ 5, src2, src2+ 3, dest); for (int j=0; j< 8; j+ +) cout << dest[j] << " "; cout << endl; return 0; }
  • 13. 결과 : 1 2 3 3 4 5 6 9 결과를 보면 알수 있듯이 merge는 순서를 유지한다. 2.6 Function Objects 어떤 알고리즘들은 인자 함수 객체를 받는 것이 있다. 함수객체는 겉으로 보기엔 템플릿 함수처럼 보인다. 그러나 실체는 ()오퍼레이터를 오버로드한 싱글 멤버함수를 (K:오버로드란 클래스에서 연산자를 함수처럼 사용하는 것을 말한다. Ex : C.Add(); -> C+ +;) 가진 템플릿 클래스 객체이다. 이것은 이해가 잘 안되지만 사용하기가 아주 편리하다. 배열을 ascending(K:오르는)이 아닌 descending으로 정렬하고자 한다고 하고 아래 예제를 보자. // sortemp.cpp #include<iostream> #include<algorithm> #include<functional> usingnamespace std; double fdata[] ={ 19.2, 87.4, 33.6, 55.0, 11.5, 42.2 }; int main() { sort ( fdata, fdata+ 6, greater<double>()); for (int j=0; j< 6; j+ +) cout << fdata[j] << " " << endl; return 0; } sort() 알고리즘은 일반적으로 ascending order로 정렬하지만 greater< >() 함수객체는 정렬순서를 뒤집는다. 따라서 결과는 87.4 55 42.2 33.6 19.2 11.5 이 외에도 산술 이나 논리를 위한 함수객체가 더 있으나, 뒤에서 자세하게 보기로 한다.
  • 14. 2.6.1 사용자 정의 함수 객체 함수객체는 이미 정의되어 있는 오퍼레이터들(+ ,< ,==,..)과 함께 기본 C+ + 타입들과 클래스만 적용이 된다. 그런데 이러한 경우가 아니라면, 즉 사용자가 작성한 함수를 함수객체로 쓰고자 한다면 어떻게 해야하지? 예로, < 오퍼레이터가 정의되지 않은 char* 문자열 비교를 위해서 비교함수를 함수 주소를 넘겨줌로써 함수객체 대신 사용할 수 있다. 아래의 코드를 보자. // sortcmp.cpp #include<iostream> #include<string> #include<algorithm> using namespace std; char *names[] ={"Geroge", "Penny", "Estelle", "Don", "Mike", "Bob"}; bool alpha_comp (char*, char*); int main() { sort (names, names+ 6, alpha_comp); for (int j=0; j< 6; j+ +) cout << names[j] << endl; return 0; } bool alpha_comp (char *s1, char* s2) // s1< s2이면 true를 리턴 { return ( strcmp ( s1, s2) < 0 ) ? true : false; } sort()에서 3번째 인자는 alpha_comp 함수의 주소이다. 위의 결과는 Bob Don Estelle George Mike Penny 이다. 즉 이름을 알파벳 순으로 정렬한 결과이다.
  • 15. 2.7 Adding _if to Algorithms 알고리즘중에는 끝에 _if가 붙은 알고리즘이 있다. 이러한 알고리즘들은 함수 객체나 함수인 속성(predicate)이라 불리는 추가 파라미터를 가진다. 예로 find()알고리즘은 어떤 특정한 값과 일치하는 값을 찾는다. 또한 find_if()알고리즘으로도 어떤 추상적 특징을 가지는 엘리먼트를 찾게 할 수도 있다. 아래의 예제 코드는 string객체를 사용한다. find_if()알고리즘은 string 오브젝트 배열에서 Don이란 값을 가지는 string을 찾는 isDon() 함수의 supprt를 받는다. 아래는 FIND_IF의 코드이다. // find_if.cpp #include<iostream> #include<string> #include<algorithm> using namespace std; string names[] ={“Geroge”,”Estelle”, “Don”, “Mike”, “Bob”}; ///////////////////////////////////////////////////////////////// bool isDon(string name) { return name == "Don"; } ///////////////////////////////////////////////////////////////// int main() { string* ptr; ptr = find_if (names, names+ 5, isDon); if (ptr == names+ 5) cout << "Done is not on the list" << endl; else cout << "Don is element " << (ptr-names) << " on the list." << endl; return 0; } ///////////////////////////////////////////////////////////////// Don은 실제 list에 있기때문에 프로그램의 결과는 Don iselement 2 on the list. 이다.
  • 16. 여기서 find_if()는 모든 엘리먼트를 isDon()으로 넘겨주고 isDon은 Don을 찾으면 true를 리턴하게 되고 find_if를 빠져나오게 된다. (K:Find()문과 마찬가지로 3번째에 있으니 시작이 0이므로 2라고 표시된다. ) 이밖에도 _if버전으로 적용될수 있는 count(), replace(),remove()등등이 있다. 2.8 The for_each()Algorithm for_earch()는 콘테이너에 있는 모든 엘리먼트를 상대로 뭔가를 할 수 있는 알고리즘이다. 그리고 또한 사용자가 직접 뭔가를 수행하기 위한 코드를 작성할 수 있다. 사용자 작성 함수는 값을 바꿀수는 없지만 참조와 display는 할 수 있다. 다음 예제는 인치를 센티미터로 바꾸고 보여주는 것이다. 2.54를 곱하는 in_to_cm()함수를 만들고 for_each() 의 세번째 파라미터로 주소를 넘겨준다. // for_each.cpp #include<iostream> #include<algorithm> using namespace std; void in_to_cm(double); int main() { double inches[] ={ 3.5, 6.2, 1.0, 12.75, 4.33 }; for_each (inches,inches+ 5, in_to_cm); cout << endl; return 0; } void in_to_cm(double in) { cout << (in * 2.54) << " "; } 결과는 8.89 15.748 2.54 32.385 10.9982
  • 17. 2.9 The transform() Algorithm transform()알고리즘은 콘테이너의 모든 엘리먼트에게 뭔가를 하고 다른 콘네이너에게 (또는 같은 콘테이너에게)이 결과를 전송한다. 사용자 작성함수는 엘리먼트에게 뭘할건지를 결정한다. 그리고 이 사용자 작성함수의 리턴값은 입력파라미터와 일치해야 한다. 다음의 예는 FOR_EACH와 비슷하지만 결과를 다른 double []로 저장한다. // tranfo.cpp #include<iostream> #include<algorithm> using namespace std; double in_to_cm(double in) { return in * 2.54; } int main() { double inches[] ={ 3.5, 6.2, 1.0, 12.75, 4.33 }; double centi[5]; double in_to_cm(double); // prototype transform (inches, inches+ 5, centi, in_to_cm); for (int j=0; j<5; j+ +) cout << centi[j] << " " << endl; return 0; } 결과는 FOR_EACH와 같다. 이제까지 단지 몇개의 알고리즘만 살폈지만 STL에는 이밖에도 아주 많은 알고리즘이 있다. 여기서는 단지 어떻게 사용하고 응용가능한지 만을 보인 것 뿐이다.
  • 18. 3 Sequential Containers 이미 앞에서 살폈듯이 STL 콘테이너에는 2가지 주요 카테고리로써 sequence 콘테이너 와 associative 콘테이너가 있다고 했다. 이번 장에서는 vector, list,deque의 3가지 시퀀스 콘테이너를 소개하고 이 콘테이너들이 어떻게 작동하고 멤버함수를 어떻게 사용하는지를 다룰 것이다. iterator에 대해 아직은 자세하게 배우지 않았기 때문에 위의 콘테이너로 어떤 동작을 수행하게 하는 것은 다루지 않는다. iterator에 대해서는 다음 장에서 자세하게 다루도록 한다. 앞으로 나올 예제들은 콘테이너를 설명하기 위해 몇가지 멤버함수들만 사용하게 될것이다. 기억해둘 것은 서로 다른 콘테이너들도 같은 이름의 멤버함수를 갖는다는 사실이다. 3.1 Vectors 벡터는 영리한 배열(smart array)이라 생각해도 좋다. 벡터는 저장소를 할당하고, 데이터의 삽입이나 삭제에 따라 크기를 확장하거나 축소시킨다. 벡터를 []오퍼레이터를 써서 배열처럼 사용해도 무방하다. 벡터에서 이러한 임의접근은 매우 빠르게 작동 한다. 또한 push를 이용해서 벡터의 끝에 데이터를 빠르게 추가한다. 데이터가 추가되면 (추가되기 직전에) 크기도 자동으로 증가한다. 3.1.1 Member Functions push_back(),size(), and operator[] 첫번째 예제로 VECTOR는 일반적인 벡터 오퍼레이션을 보여준다. //vector.cpp #include<iostream> #include<vector> using namespace std; int main() { vector< int> v; // ints 벡터를 생성한다. v.push_back(10); // 배열의 끝에 데이터를 추가한다. v.push_back(11); v.push_back(12); v.push_back(13); v[0] = 20; v[3] = 23; // 새로운 값으로 대치 for (int j=0; j< v.size(); j+ +) // 벡터의 내용물을 출력 cout << v[j] << " " ; return 0; }
  • 19. 벡터 v를 생성하기 위해 벡터의 기본생성자(no-argument)를 사용했다. 콘테이너의 크기를 알수 없으므로 0부터 시작한다. push_back()멤버함수는 벡터의 끝에 값을 추가한다. list나 queue처럼 벡터의 시작부(index=0)에는 데이터를 삽입 할 수 없다. 예제에선 10, 11, 12, 13을 추가 했으므로 v[0]은 10, v[1] = 11, v[2] = 12, v[3]=13이다. 일단 벡터에 데이터가 들어가면 [] 오퍼레이터를 통해 읽고 쓰기가 가능해진다. 예제에서 첫번째 값을 v[0]=20으로 바꾸고, v[3] = 23으로 바꿨다. 따라서 예제의 결과는 20 11 12 23 이 된다. size() 멤버함수는 벡터의 엘리먼트 갯수를 리턴한다. 예제에서 다루지 않은 다른 멤버함수로는 max_size()가 있다. 이 함수는 콘테이너가 확장가능한 최대 크기를 리턴한다. 이 수치는 콘테이너의 데이터 타입에 따라 틀려지 게 된다. vector< int>의 max_size()는 1,073,741,823을 리턴할 것이다. 3.1.2 Member Functions swap(),empty(), back() and pop_back() 다음의 VECTOR는 벡터의 추가적인 생성자와 멤버함수에 대해서 다룬다. //vectcon.cpp #include< iostream> #include< vector> using namespace std; int main() { double arr[] ={1.1, 2.2, 3.3, 4.4}; vector< double> v1(arr,arr+ 4); // 벡터를 arr로 초기화 시킨다. vector< double> v2(4); // 크기가 4인 empty 벡터를 생성한다 v1.swap (v2); // v1과 v2의 값을 맞바꾼다. while (!v2.empty()) // v2가 빌(empty)때 까지 { cout << v2.back() << " "; // 마지막 엘리먼트를 display v2.pop_back(); // 마지막 엘리먼트를 삭제 } return 0; }
  • 20. 위에서 두가지 새로운 백터의 생성자를 사용하였다. 첫번째 벡터 v1은 보통 C+ + 배열 을 가지고 초기화 시켰고, 두번째 벡터 v2는 크기만 결정하였다. swap()멤버 함수는 엘리먼트의 order를 유지하면서 모든 엘리먼트를 바꾸게 된다. 이 프로그램에서 v2와 v1의 값을 바꿨기 때문에 v2를 display시켜보면 결과는 4.4 3.3 2.2 1.1 이 된다. back() 멤버함수는 벡터의 맨 끝에 있는 엘리먼트를 리턴한다. pop_back() 멤버함수는 벡터의 맨 끝에 있는 엘리먼트를 제거한다. 따라서 루프는 메번 다른 엘리먼트를 출력 하게 된다. swap() 같은 멤버함수는 또한 알고리즘에도 존재한다. 이런 경우는 알고리즘 버전보다도 해당 콘테이너의 멤버함수가 더 효율적이기 때문에 사용된 것이다. 때때로 알고리즘의 swap()을 사용해서 서로다른 콘테이너의 엘리먼트들을 교환할수도 있다. 3.1.3 Member Functions insert()and erase() insert()와 erase()멤버함수는 콘테이너상의 임의의 위치에 엘리먼트를 삽입하고 삭 제 한다. 이러한 함수들은 벡터를 사용할 경우 insert나 remove를 위해 모든 엘리먼트 들을 이동시켜야 하기 때문에 매우 효율적이지 못하다. 그러나 삽입과 삭제는 그럼에도 불구하고 속도외에는 효율적인 방법이다. 이번 예제는 멤버함수를 어떻게 사용하는 지를 보인다. //vectins.cpp #include<iostream> #include<vector> using namespace std; int main() { int arr[] ={100, 110, 120, 130}; vector< int> v(arr, arr+ 4); // 벡터를 arr로 초기화 시킨다. cout << "Before insertion : "; for (int j=0; j< v.size(); j+ +) cout << v[j] << " "; cout << endl; v.insert (v.begin()+ 2, 115);
  • 21. cout << "After insertion : "; for (j=0; j< v.size(); j+ +) cout << v[j] << " "; cout << endl; v.erase (v.begin()+ 2); cout << "After erasure : "; for (j=0; j< v.size(); j+ +) cout << v[j] << " "; cout << endl; return 0; } 결과: Before insertion: 100 110 120 130 After insertion: 100 110 115 120 130 After erasure: 100 110 120 130 3.2 Lists STL의 list 콘테이너는 doubly linked list로써, 각 엘리먼트들은 다음 엘리먼트의 포인터를 가지고 있을 뿐만 아니라 이전 엘리먼트의 포인터도 가지고 있다. 콘테이너는 양쪽으로 빠른 접근을 위해 엘리먼트의 앞쪽과 뒤쪽 모두를 저장하고 있다. 3.2.1 Member Functions push_front(),front(), and pop_front() 첫번째 예제인 LIST는 데이터를 삽입하고 읽어오고 빼내는 방법을 보여준다. // list.cpp #include<iostream> #include<list> using namespace std; int main() { list<int> ilist; ilist.push_back(30); ilist.push_back(40); ilist.push_front(20); ilist.push_front(10); int size = ilist.size();
  • 22. for (int j=0; j<size; j+ +) { cout << ilist.front() << " "; ilist.pop_front(); } cout << endl; return 0; } 데이터를 위,앞에서 추가 한다음 앞쪽부터 display하고 삭제하였다. 결과는 10 20 30 40 이다. push_front(),pop_front(),front()멤버함수는 이미 앞에서 다룬 벡터의 push_back(), pop_back(), back()과 유사하다. (K:다만 콘테이너의 앞부분을 처리 한다는것만 다르다) 리스트는 랜덤 액세스 속도가 매우 느리기 때문에 리스트로 랜덤 액세스는 피해야 함을 주목해야한다. 이러한 이유때문에 리스트에는 [] 오퍼레이터가 없다. 정말로 랜덤 액세스가 필요하면 vector나 deque를 사용하기 바란다. 리스트는 중간에 엘리먼트를 삽입 삭제가 빈번하게 발생할 때 유용하다. 엘리먼트의 추가, 삽입을 위해 모든 엘리먼트의 포인터를 이동시켜야 하기 때문에 vector나 deque에 비해 효율성은 떨어지지만 포인터만 이동하므로 빠른편에 속한다. (문제점은 추가,삭제 포인터를 찾는데 시간이 걸린다는 것이다) 3.2.2 Member Functions reserve(),merge(), and unique() 어떤 멤버함수들은 list에만 있는 것이 있지만, 결과적으로 같은 기능을 한다. 예제 LISTPLUS는 이러한 함수들에 대해 다룬다. // listplus.cpp #include<iostream> #include<list> using namespace std; int main() { int j; list <int> list1, list2; int arr1[] = {40, 30, 20, 10 }; int arr2[] = {15, 20, 25, 30, 35 };
  • 23. for (j=0; j<4; j+ +) list1.push_back( arr1[j]); for (j=0; j<5; j+ +) list2.push_back( arr2[j]); list1.reverse(); // list1의 순서를 뒤집는다. list1.merge(list2); // list1에 list2를 포함시킨다. list1.unique(); // 중복되는 값을 제거한다. 20, 30 int size = list1.size(); while (!list1.empty()) { cout << list1.front() << " "; list1.pop_front(); } cout << endl; return 0; } 첫번째 리스트는 역순으로 입력되었기에 reverse()를 통해 정렬을 하였다(이 동작은 양 끝을 액세스 할 수 있기때문에 매우 빠르다.). merge()를 수행하기 위해서 먼저 두 리스트를 정렬해야만한다. 여기까지 결과는 10 20 30 40 15 20 25 30 35 이다. 이제 merge() 함수로 list1과 list2를 병합하면 정렬 순서에 따라 병합된다. 10 15 20 20 25 30 30 35 40 마지막으로 list1에 unique() 멤버함수를 적용시킨다. 이 함수는 근접한 엘리먼트들이 같은 값을 가질 경우 이를 제거하고 하나만 남긴다. 따라서 list1의 내용은 10 15 20 25 30 35 40 이 된다. 이 값을 display하기 위해 front()와 pop_front()를 이용하였다.
  • 24. 3.3 Deques 어떤 면에서 deque는 vector와 비슷하고 또 어떤 면에서는 list와 비슷하다. vector처럼 [] 오퍼레이터로 랜덤 액세스를 지원하고 뿐만 아니라 list처럼 엘리먼트를 앞뒤에서 액세스가 가능하다. 즉, deque는 push_front(), pop_front(), front()를 지원하는 double-ended vector의 일종이다. 그렇지만 메모리는 벡터와 큐와 다르게 할당이 된다. 벡터는 항상 연속된 메모리 공간에 할당이 된다. 벡터의 크기가 너무 커지면 크기가 알맞는 메모리 공간으로 옮겨야 한다. 반면에 deque는 비연속적인 메모리(called segment)에 저장될 수 있다. capacity() 멤버함수는 엘리먼트 벡터가 이동없이 할당될 수 있는 최대 크기를 리턴한다. 그렇지만 deque에서는 메모리가 이동될 필요가 없기때문에 capacity()가 정의되어 있지 않다. //deque.cpp #include <iostream> #include <deque> using namespace std; int main() { deque<int> deq; deq.push_back(30); // push items on back deq.push_back(40); deq.push_back(50); deq.push_front(20); // push items on front deq.push_front(10); deq[2] = 33; // change middle item for (int j=0; j<deq.size(); j+ +) cout << deq[j] << ' '; // display items cout << endl; return 0; } 앞에서 이미 push_back(), push_front(), operator []를 배웠기 때문에 더 이상 설명이 필요없다. deque도 다른 콘테이너처럼 똑같이 작동한다. 결과는 10 20 33 40 50 이다.
  • 25. 4. Iterators 이터레이터는 조금 신비스럽게 보이며서 STL의 핵심이라 할 수 있겠다. 이번 장에서는 우선 스마트 포인터와 알고리즘-콘테이너를 연결해 주는 이터레이터의 두가지 역할에 대해 다루게 된다. 물론 사용법은 몇몇 예제를 통해 보게 될것이다. 4.1 Iterators as Smart Pointers 종종 콘테이너의 모든(또는 일부 영역) 엘리머트를 가지고 작업을 해야할 때가 있다. 콘테이너에 있는 각각의 엘리먼트를 display하거나 total을 구하기 위해 값을 더하기도 한다. 전통 C+ + 배열에서 이러한 오퍼레이션들은 포인터를(또는 [] 오퍼레이터) 이용하여 구현되었다. 예를 들어 아래의 코드는 float 배열을 순회(iterates)하고 각 엘리머트의 값을 보여준다. float * ptr = start_address; for (int j=0; j<SIZE; j+ +) cout << *ptr+ +; 포인터가 가리키고 있는 값을 가져오기 위해 * 오퍼레이터를 이용하였고, 다음 포인터를 가리키기 위해 + + 오퍼레이터를 이용하였다. 4.1.1 Ordinary Pointers Underpowered 그러나, 고급 콘테이너들에서, 평범한 C+ + 포인터는 유용하지 못하다. 만일 콘테이너에 저장된 아이템이 연속(contigious) 메모리에 저장되어 있지 않다면 포인터 취급이 더욱더 복잡해질 것이다. 예를 들어, 링크드 리스트에서 다음 아이템으로 이동할때 우리는 다음 아이템이 바로 옆에 있다고 예측할 수가 없기때문에 처음부터 포인터를 따라서 해당 위치를 먼저 찾아가야만 한다. 또한 포인터 변수에 어떤 콘테이너의 엘리먼트 주소도 저장하기를 원한다. 이 주소를 이용하여 이후에 이 값을 액세스할 수 있기 때문이다. 콘테이너의 중간에서 값을 삽입하거나 삭제했을 경우 이 콘테이너에 무슨 일이 발생할까? 아마도 콘테이너의 콘텐츠가 재정렬 되지 않는 이상 연속성이 끊어지게 될것이다. 삽입이나 삭제시 저장된 포인터값의 변경을 신경쓰지 않아도 된다면 참 좋을 것이다. 이러한 문제에 대한 한가지 해결책은 "smart pointer"클래스를 만든는 것 뿐이다. 이런 종류의 클래스 오브젝트는 기본적으로 멤버변수를 전통적인 포인터로 감싼다. 그리고 + +와 * 오퍼레이터들은 콘테이너에서 비록 엘리먼트가 비연속일지라도 어떻게 엘리먼트를 다룰지를 오버로드한다. 아래의 예제는 어떻게 구현하는지 골격만 나타낸것이다.
  • 26. class SmartPointer { private: float *p; // 평범한 포인터 public: float operator*() {} float operator+ +{} }; void main() { ... SmartPointer sptr = start_address; for (int j=0; j<SIZE; j+ +) cout << *ptr+ +; } 4.1.2 Whoes Responsibility? (누가 책임지지?) SmartPointer 클래스는 콘테이너에 소속(embed) 되어야만 하나? 아니면 독립적이어야만 하나? STL이 선택한 해결점은 SmartPointer(이터레이터라고 불리는)를 만들기 위해 클래스와 완전히 독립되게 만들었다(사실 템플릿화 한것이다). 4.1.3 인터페이스로써의 이터레이터(Iterators as an Interface) 이터레이터가 콘테이너에서 스마트 포인터처럼 작동하는 것 말고, 이터레이터는 STL에서 또 다른 중요한 기능을 제공한다. 그것은 바로 어떤 콘테이너가 어떤 알고리즘이 사용될 수 있는지를 결정할 수 있게하는 것이다. 이 기능이 왜 필요할까? 이론적 센스로 생각한다면 모든 콘테이너에 모든 알고리즘을 적용할 수 있을 것이라는 생각이 들것이다. 사실 많은 알고리즘들이 STL의 거의 모든 콘테이너에 적용할 수 있다. 그러나 어떤 콘테이너에게는 매우 비효율적(즉 slow)이 될 수도 있다. sort()알고리즘을 예로 들자면, 정렬을 하기 위해서는 랜덤액세스가 필요하다. 랜덤액세스가 지원되지 않는 콘테이너 에서는 무조건 처음부터 탐색해서 정렬을 시도해야만 할것이다. -_-; 비슷한 예로 reverse() 알고리즘은 뒤에서(backword)부터 액세스가 지원되어야 할 것이다. 이터레이터는 놀랍도록 멋진 방법으로 콘테이너에 적합한 알고리즘을 적용시킨다. 앞에서 말했듯이 콘테이너를 컴퓨터와 프린터를 연결하는 것처럼 케이블이라 생각해보자. 케이블 한쪽 끝에 콘테이너를 플러그하고 다른 한쪽엔 알고리즘을 플러그한다. 그렇지만 모든 케이블들이 모든 콘테이너를 플러그할 수는 없고 모든 알고리즘을 플러그 할수도 없다.
  • 27. 만일 주어진 콘테이너에서 너무 무리한 알고리즘을 사용하려 한다면, 알고리즘과 연결하기 위한 케이블(이터레이터)을 찾을 수 없다. 이걸 컴파일 해보면 아마 컴파일러 에러를 보게될것이다. 그렇다면 이스킴을 작동시킬려면 얼마나 많은 종류의 케이블(이터레이터)들이 필요하게 될까? 통털어서 5개만 있으면 된다. 그림 4.3은 이 5가지 카테고리를 보여준다. 그림은 정교하게 하기 위해 밑에서 부터 위로 정렬된 것이다. 그렇다고 상속관계는 아니다. + -----------------+ | Random Access | + -----------------+ | + -----------------+ | Bidirectional | + -----------------+ | + -----------------+ | Forward | + -----------------+ | | + -+ + -+ | | + -------------+ + -------------+ | Input | | Output | + -------------+ + -------------+ 그림 1.3 이터레이터 카테고리 만일 어떤 알고리즘이 reading하는데(writing제외) 콘테이너의 전방참조만 필요하다면 알고리즘과 콘테이너를 연결하기 위한 이터레이터는 Input 이터레이터가 될것이다.사실 Input 이터레이터는 콘테이너뿐만 아니라 파일에서 읽을때나 cin으로 읽을 때도 적용될 수 있다. 알고리즘이 콘테이너의 전방참조가 필요하고 읽는 것 대신에 쓰는게 필요하다면 output 이터레이터가 연결될 것이다. Output 이터레이터도 Input이터레이터와 마찬가지로 파일이나 cout으로 write할때 일반적으로 사용된다. 알고리즘이 콘테이이너의 전방순회를 하고 읽기/쓰기 둘다 지원하게 싶을 때는 forward 이터레이터를 사용한다. 알고리즘이 콘테이너의 전방/후방 순회를 모두 지원하게 하고 싶을 때는 bidirectional 이터레이터를 사용하면 된다.
  • 28. 마지막으로, 알고리즘이 콘테이너의 아무 엘리먼트를 즉시 사용할 수 있게 하고 싶을 때는 random 이터레이터를 사용한다. 랜덤 액세스 이터레이터는 아무 엘리먼트나 액세스 할 수 있는 배열과 비슷하다. 랜덤 액세스 이터레이터만이 다음과 같은 산술 오퍼레이터를 가지고 있다. iter2 = iter1 + 7; Table 1.7 Capabilities of Different Iterator Categories ======================================================================== Step Step Random Forward Read Write Back Access Iterator Type + + value=*i *i=value -- [n] ======================================================================== Random access x x x x x ------------------------------------------------------------------------ Bidirectional x x x x ------------------------------------------------------------------------ Forward x x x ------------------------------------------------------------------------ Output x x ----------------------------------------------------------------------- input x x ======================================================================== 위Table을 보면 알 수 있듯이 모든 이터레이터들이 전방순회를 하는 + +오퍼레이터를 지원한다. input 이터레이터는 이퀄표시 오른쪽에서 * 오퍼레이터를 사용할 수 있다.(왼쪽은 안된다) value = *iter; output 이터레이터는 * 오퍼레이터를 오로지 오른쪽에서만 쓸 수 있다. *iter = value; 전방순회 이터레이터는 읽기/쓰기 모두 다룰 수 있고, 두방향(bidirectional) 이터레이터는 증감을 이용하여 순회할 수 있다. 랜덤 액세스 이터레이터는 간단한 산술 오퍼레이터인 - 나 + 처럼 [] 오퍼레이터를 써서 아무 엘리먼트나 즉시 액세스가 가능하다. 알고리즘은 항상 필요한 것보다 더 많은 기능을 가질 수 있다. 만일 전방순회가 필요할때, 두방향 이터레이터나 랜덤 액세스 이터레이터를 사용해도 무방하다.
  • 29. 4.2 Matching Algorithms with Containers 이터레이터는 알고리즘과 콘테이너를 연결해주기 때문에 케이블 개념으로 이터레이터를 사용했었다. 그럼 이제 상상의 케이블 양끝에 촛점을 맞춰보자. (양끝은 콘테이너측과 알고리즘 측을 얘기한다) 4.2.1 이터레이터와 콘테이너의 연결 (Plugging the cable into a Container) 만약 스스로 기본 STL 콘테이너 범위로만 한정하여 생각하고 있다면, 단 두가지 이터레이터만 쓸 수 있을 것이다. 아래의테이블 1.8을 보면 벡터와 큐는 어떤 이터레이터든 수용할 수 있고, 반면에 list, set, multiset, map, multimap은 랜덤 이터레이터를 제외한 다른 것들만 수용할 수 있음을 알수 있다. Table 1.8 Iterator Types Accepted by Containers ======================================================================== vector List Deque Set Multiset Map Multimap ======================================================================== Random access x x ------------------------------------------------------------------------ Bidirectional x x x x x x x ------------------------------------------------------------------------ Forward x x x x x x x ------------------------------------------------------------------------ Output x x x x x x x ------------------------------------------------------------------------ input x x x x x x x ======================================================================== 그렇다면 STL은 어떻게 주어진 콘테이너에 알맞는 이터레이터를 수행할 수 있을까? 프로그래머는 이터레이터를 정의하고, 반드시 어떤 콘테이너가 사용할것인지를 명시해줘야만 한다. 예를 들어, int형을 수용하는 리스트를 선언 한다고 하면, lint<int> iList; // list of ints 라고 선언하고, 이 리스트를 위한 이터레이터를 선언하기 위해 다음처럼 할것이다. list<int>::iterator iter; // iterator to list-of-ints
  • 30. 이렇게 해주면, STL은 이 이터레이터를 자동으로 list에게 요구되는 bidirectional(쌍방) 이터레이터로 만들어 준다. vector나 deque에 대한 이터레이터는 자동으로 랜덤 액세스 이터레이터로 만들어 줄것이다. 이러한 자동선택 프로세스는 특정한 클래스에 적합하게하기 위해 보다 일반화된 클래스로부터 상속 받은 특정한 이터레이터 클래스를 가지게 함으로써 구현된다. 따라서 vector와 queue의 이터레이터는 random_access_iterator로부터 상속을 받고, 반면에 list의 이터레이터는 bidirectional_iterator 클래스로부터 상속된다. 그럼 이제 콘테이너가 다양한 이터레이터(케이블)의 끝과 어떻게 매치되는지를 살펴보자. 4.2.2 이터레이터와 알고리즘의 연결 (Plugging the Cables into th Algorithm) 이터레이터의 한쪽 끝이 어떻게 콘테이너와 연결되는지를 앞에서 봤으니까 이제 이터레이터의 다른 한쪽을 볼 차례다. 이터레이터가 알고리즘과 어떻게 연결될것인가? 모든 알고리즘들은 콘테이너의 엘리먼트들에게 무엇을 어떻게 할것인가에 따라서 이터레이터를 필요로 하게 된다. 알고리즘이 콘테이너의 엘리먼트에 대해 임의의 위치에서 액세스가 가능해야 하다면 랜덤 액세스 이터레이터가 필요하게 될것이다. 또는 이터레이터를 통해 단지 순차적 전방 액세스만 원한다면 덜 파워풀한 forward 이터레이터가 사용될 수 있을 것이다. 테이블 1.9는 알고리즘과 필요로하는 이터레이터를 나타낸다. Table 1.9 Type of Iterator Representative Algorithms ============================================================= Bidirec- Random Input Output Forward tional Access ============================================================= for_each x ------------------------------------------------------------- find x ------------------------------------------------------------- count x ------------------------------------------------------------- copy x x ------------------------------------------------------------- replace x ------------------------------------------------------------- unique x -------------------------------------------------------------
  • 31. reverse x ------------------------------------------------------------- sort x ------------------------------------------------------------- nth_element x ------------------------------------------------------------- merge x x ------------------------------------------------------------- accumulate x ============================================================= 비록 각 알고리즘들이 어떤 한정적 이터레이터만 요구하더라고 이보다 더 파워풀한 이터레이터가 사용될 수 있다. replace() 알고리즘은 forward 이터레이터가 필요하지만 bidirectional(양방향) 이나 random access 이터레이터도 사용할 수 있다 . 이제 알고리즘이, 컴퓨터에 있는 케이블 코넥터처럼, 고정할 수 있는 핀을 가진 코넥터를 가졌다고 상상해보자. (그림 1.4) 랜덤 액세스를 필요로하는 것은 5개의 핀을 가졌고, 양방향(bidirectional) 액세스가 필요한 것은 4개의 핀이 있으며, 전방(forward) 참조를 요하는 것은 3개의 핀이 있다. Random_access container iterator Algorithm + ----------+ |() -| + ------------+ | vector | ----|() -|----| reverse() | + ----------+ |() OK -| + ------------+ |() -| |() bidirectional iterator + ----------+ |() -| + ------------+ | List |----|() -|----| sort() | + ----------+ |() No -| + ------------+ |() Good -| -| [그림 1.4 콘테이너와 알고리즘을 연결하는 이터레이터]
  • 32. 알고리즘과 연결될 이터레이터의 끝은 몇 개의 구멍을 가진 코넥터를 가진다. 5개의 구멍을 가진 이터레이터는 5개의 핀을 가진 알고리즘과 연결되고, 또한 4개나 그 이하의 핀을 갖는 알고리즘과도 연결될 수가 있다. 그렇지만 4개의 구멍을 가지는 이터레이터 (bidirectional)와 5개의 핀이 있는 랜덤 액세스 알고리즘과는 연결 시킬 수 없다. 따라서 랜덤 액세스 이터레이터를 가지는 벡터와 데큐는 bidirectional 이터레이터를 지니고, 양방향 이터레이터 보다 덜 파워풀한 이터레이터와 붙을 수 있다. 4.3 The Tables Tell the Story 테이블 1.8과 1.9로부터 어떤 알고리즘이 어떤 콘테이너와 작동되는지를 이해할 수 있을것이다. 예를들어, 테이블 1.9는 sort() 알고리즘이 랜덤 액세스 이터레이터를 필요로 한다는 것을 알 수 있을 것이다. 테이블 1.8을 보면 랜덤 액세스 이터레이터가 벡터와 데큐만을 다룰 수 있음을 알 수 있다. 테이블을 보면 알 수 있듯이 sort()알고리즘을 list, set, map등에서 사용 할 수 없다. 이러한 콘테이너들이 랜덤 액세스 바로 밑 단계의 bidirectional 이터레이터를 사용하기 때문에 랜덤 액세스 이터레이터가 필요없는 어떤 알고리즘이든지 아무 STL 콘테이너와 작동이 된다. (STL에서 singly-linked list는 forward 이터레이터만 사용하게 될것이다. 따라서 reverse() 알고리즘을 사용할 수 없게 된다.) 4.3.1 Overlapping Member Functions and Algorithms (멤버함수와 알고리즘의 중첩) 때때로 같은 이름을 가지는 멤버함수나 알고리즘 중에서 선택해야할 경우가 있다. find()알고리즘은 input 이터레이터만 필요하다. 그래서 어떤 콘테이너든 사용이 가능하다. 그렇지만 set과 map은 자신만의 find() 멤버함수를 가지고 있다. 과연 어떤 버전의 find()를 사용할것인가? 일반적으로 멤버함수 버전이 있다는 것은 알고리즘이 그 콘테이너에 알고리즘이 비 효율적이기 때문에 따로 존재하는 것이다. 그러므로 이러한 경우엔 멤버함수를 사용하면 된다. 4.4 Iterators at Work 지금까지 이터레이터에 대해서 복잡하게 떠들었지만, 백문이불여일견! 직접 사용해보면 의외로 간단하다. 이미 앞에서 몇가지 예를 살펴보았듯이 일반적으로 사용되는 이터레이터들은 콘테이너의 begin(), end()가 이터레이터의 값을 리턴하게된다. 여태까지 이러한 함수들이 리턴하는 이터레이터 값이 포인터로만 취급된다고 말해었지만, 이젠 이러한 이터레이터들이 다른 함수에서 어떻게 사용되는지 확실하게 살펴보자. 4.4.1 Data Access 랜덤 액세스 이터레이터를 제공하는(vector, deque) 콘테이너에서는 [] 오퍼레이터를 이용하여 쉽게 엘리먼트를 참조했다. 랜덤 액세스를 지원하지 않는 list 같은 콘테이너는 이것과는 다른 방식의 참조 방법이 필요하다. 앞에서 들었던 예 중에 아이템을 하나씩 pop 해서 리스트의 내용을 display하기위해 "LIST"와 "LISTPLUS" 예제에서 destructive readout을 사용했었다.
  • 33. 이걸 좀더 심화하여 콘테이너에 정의된 이터레이터를 사용해보자. // listout.cpp // iterator and for loop for output #include <iostream> #include <list> #include <algorithm> using namespace std; int main() { int arr[] = {2, 4, 6, 8 }; list<int> theList; for (int k=0; k<4; k+ +) // 배열의 엘리먼트로 리스트를 채운다. theList.push_back(arr[k]); list<int>::iterator iter; // iterator to list-of-ints for (iter=theList.begin(); iter != theList.end(); iter+ +) cout << *iter << ' '; // display the list cout << endl; return 0; } 이 프로그램은 단순히 theList 콘테이너의 내용을 보여주는 것이다. 결과는 2 4 6 8 이다. 위에서 콘테이너의 타입과 매치시키기위해 list<int>타입의 이터레이터를 선언했다. 마치 포인터를 사용하듯이 이터레이터를 사용하기 전에 먼저 초기값을 설정해야 한다. for 루프에서 iter = theList.begin() 구문으로 리스트의 첫부분으로 초기화를 해주었다. ++오퍼레이터로 포인터를 증가시켜 *오퍼레이터로 이터레이터가 가리키고 있는 값을 역참조했다. 그리고 루프의 종료는 이터레이터가 콘테이너의 끝에 다다랐나를 체크함으로써 처리를 해주었다. 또한 for 루프와 기능적으로 동일하게 작동하게 하기 위해서 while 루프를 이용할 수도 있다.
  • 34. iter = theList.begin(); while ( iter != theList.end() ) cout << *iter+ + << ' '; *iter+ + 문법은 포인터와 같다. 4.4.2 Data Insertion 아래의 LISTFILL 예제를 통해 콘테이너의 엘리먼트가 이미 존재하는 곳에 데이터를 삽입하는 과정을 살펴보자. // listfill.cpp // uses iterator to fill list with data #include <iostream> #include <list> using namespace std; int main() { list<int> iList(5); // empty list holds 5 ints list<int>::iterator it; // iterator int data = 0; // fill list with data for (it=iList.begin(); it != iList.end(); it+ +) *it = data + = 2; // display the list for (it=iList.begin(); it != iList.end(); it+ +) cout << *it << ' '; cout << endl; return 0; } 첫번째 for 루프는 콘테이너에 int 값은 2, 4, 6, 8, 10으로 채운다. 그리고 두번째 for 루프는 이 값들을 보여준다.
  • 35. 4.4.3 Algorithms and Iterators 우리가 얘기했던 알고리즘들은 인자로 이터레이터를 이용한다. 그리고 때론 값을 리턴하기도 한다. ITERFIND 예제는 find() 알고리즘이 리스트에 적용되는 걸 보여준다. (이미 알고있겠지만 find()알고리즘은 오직 input 이터레이터만 필요하기때문에 list와 함께 쓰일 수 있다.) // ITERFIND.cpp // find() returns a list iterator #include <iostream> #include <list> #include <algorithm> using namespace std; int main() { list<int> iList (5); // empty list holds 5 ints list<int>::iterator it; // iterator int data = 0; // fill list with data for (it=iList.begin(); it != iList.end(); it+ +) *it = data + 2; // 2, 4, 6, 8, 10 it = find (iList.begin(), iList.end(), 8); if (it != iList.end()) cout << "nFound 8.n"; else cout << "nDid not find 8.n"; return 0; } find() 알고리즘은 3개의 인자를 취한다. 첫번째와 두번째 인자는 검색될 범위를 나타내는 이터레이터이고, 세번째 인자는 찾을 값이다. find()가 iList.end()를 리턴했다면 값을 찾지 못하고 끝에 도달한 것이고 그렇지 않다면 그 값이 있는 곳을 가리키고 있을 것이다. 위의 결과는 아래와 같다. Found 8.
  • 36. 그렇다면 검색한 값 8이 콘테이너에서 몇번째 위치에 있는지 어떻게 알수 있을까? 아마도 일치되는 아이템의 offset에서 시작 이터레이터를 빼주면 될것이라고 생각할 것이다. (it - iList.begin()). 그러나 이것은 리스트의 이터레이터로 사용된 올바른 오퍼레이션이 아니다. 리스트 이터레이터는 단지 bidirectional 이터레이터다. 따라서 여기에 산술 오퍼레이션을 수행할 수 없고 랜덤 액세스를 지원하는 vector나 deque에서나 산술 오퍼레이션을 쓸 수 있다. 그러므로 iList가 아니라 벡터 v라면 위의 코드를 다시 써서 검색 위치를 나타낼 수 있다. it = find (v.begin(), v.end(), 8); if (it != v.end()) cout << "nFound 8 at location " << (it - v.begin()); else cout << "nDid not find 8."; 위의 결과는 Found 8 at location 3 이 될 것이다. 인자로 이터레이터를 받는 다른 예제를 보자. vector를 copy() 알고리즘으로 다른 vector로 복사 하는데 인자로 복사원본의 범위와 목적 콘테이너를 적어준다. // itercopy.cpp // uses iterators for copy() algorithm #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int beginRange, endRange; int arr[] = { 11,13,15,17,19,21,23,25,27,29}; vector<int> v1 (arr, arr+ 10); // 벡터 초기화 vector<int> v2 (10); // 초기화되지 않은 벡터 cout << "Enter range to be copied (example: 2 5) "; cin >> beginRange >> endRange;
  • 37. vector<int>::iterator iter1 = v1.begin() + beginRange; vector<int>::iterator iter2 = v1.begin() + endRange; vector<int>::iterator iter3; // copy from v1 to v2 iter3 = copy (iter1, iter2, v2.begin()); // iter3에는 마지막 아이템이 복사된다. iter1 = v2.begin(); while (iter1 != iter3) cout << *iter1++ << ' '; cout << endl; return 0; } 범위를 입력해주면 결과는 Enter range to be copied (example: 2 5): 3 6 17 19 21 이 될 것이다. v2의 모든 아이템을 보여줄 필요없이 복사된 것만 보여주면 된다. 다행히도 copy() 알고리즘은 복사될 콘테이너의 마지막 포인터(이터레이터)를 리턴한다. 이 프로그램은 이 값을 이용하여 복사된 값들을 display하게 했다. 4.5 Speialized Iterators 이번 장에서는 특별한 이터레이터 형태 2가지를 살펴본다. 첫번째는 흥미있는 방법으로 이터레이터의 작동을 변경할 수 있는 이터레이터 어뎁터와 마치 이터레이터 처럼 동작하는 입력과 출력 스트림을 허용하는 스트림 이터레이터들이다. 4.5.1 Iterator Adapters STL의 이터레이터는 보통 3개의 변종을 제공한다. 이런것들에는 reverse iterator, insert iterator, raw storage iterator가 있다. reverse iterator는 콘테이너의 내용을 역순으로 참조하기위해 사용되고, insert iterator는 copy()나 merge()같은 알고리즘이 콘테이너의 내용을 변경시키고자 할 때 사용된다. 따라서 데이터가 있는 곳에 덮어쓰게 된다. 마지막으로 raw storage iterator는 초기화되지 않은 메모리로 결과를 저장하고자 할때 사용하면 된다. 그러나, raw storage iterator는 특별한 경우에만 사용된다. 그러므로 이 내용은 다루지 않을 것이다.
  • 38. 4.5.1.1 Reverse Iterators 콘테이너의 끝에서 부터 역순으로 반복하기를 원한다고 가정해보자. 언뜻 생각하기에 다음처럼 하고 싶을 것이다. list<int>::iterator iter; iter = iList.end(); while ( ier != iList.begin() ) cout << *iter-- << ' '; 하지만 불행하게도 위의 코드는 돌아가지 않는다. 이유는 단 한가지, 범위가 틀리기 때문이다. (n-1 부터 0까지 여야 되지만 위의 코드는 n 부터 0까지 가 된다) 역순으로 반복하고 싶을때는 reverse iterator를 사용하면 된다. ITEREV 프로그램은 역순으로 데이터를 보여주기 위해 reverse iterator가 사용된 경우를 보여준다. // iterev.cpp // demonstrates reverse iterator #include <iostream> #include <list> using namespace std; int main() { int arr[] = {2, 4, 6, 8, 10}; list <int> theList; for (int j=0; j<5; j+ +) theList.push_back( arr[j] ); list< int>::reverse_iterator revit; revit = theList.rbegin(); while (revit != theList.rend() ) cout << *revit++ << ' '; cout << endl; return 0; } 이 프로그램의 결과는 10 8 6 4 2
  • 39. 가 된다. reverse iterator를 사용할때 rbegin()과 rend()를 사용한다. (이걸로 정상적 순서로된 이터레이터에 적용하면 안된다. -_-;) 당황스럽겠지만 콘테이너의 끝에서부터 시작하기 위해 멤버함수는 rbegin()을 사용해야하고 이터레이터를 --하지 말고 + +해야 한다. reverse iterator는 항상 rbegin()으로 시작하고 rend()로 끝나고, 이터레이터를 증가 시켜야한다. 4.5.1.2 Insert Iterators copy() 같은 일부 알고리즘들은 존재하는 내용위에 덮어쓰는 경우가 있다. COPYDEQ 프로그램은 다른 데큐로 복사하는 예를 보여준다. // copydeq.cpp // demonstrates normal copy with deques #include <iostream> #include <deque> #include <algorithm> using namespace std; int main() { int arr1[] = {1, 3, 5, 7, 9}; int arr2[] = {2, 4, 6, 8, 10}; deque <int> d1; deque <int> d2; for (int j=0; j<5; j+ +) { d1.push_back( arr1[j] ); d2.push_back( arr2[j] ); } // d1 을 d2로 복사한다. copy(d1.begin(), d1.end(), d2.begin()); for (int k=0; k<d2.size(); k+ +) cout << d2[k] << ' '; cout << endl; return 0; } 이 프로그램의 결과는 1 3 5 7 9 가 된다.
  • 40. d2를 들여다 보면 d2가 가지고 있던 내용들이 d1의 겻들로 덮어 쓰여진 것이다. 이런 동작은 일반적인 것이다. 하지만 때로는 copy() 보다는 콘테이너에 새로운 엘리먼트를 추가하고 싶을때도 있을 것이다. 이때는 insert 이터레이터를 사용하면 된다. 이것의 세가지 변종을 보자 ● back_inserter: 뒤에다 new 엘리먼트를 넣는다. ● front_inserter: 앞에다 new 엘리먼트를 넣는다. ● inserter : new 아이템을 원하는 위치에 넣는다. DINSITER 프로그램은 back_inserter 의 사용 예를 보여준다. // dinsiter.cpp // demonstrates insert iterators with queues #include <iostream> #include <deque> #include <algorithm> using namespace std; int main() { int arr1[] = {1, 3, 5, 7, 9}; int arr2[] = {2, 4, 6,}; deque <int> d1; deque <int> d2; for (int i=0; i<5; i+ +) d1.push_back( arr1[i] ); for (int j=0; j<3; j+ +) d2.push_back( arr2[j] ); // d1을 d2로 복사하는데 순서는 역순이된다. copy (d1.begin(), d1.end(), back_inserter(d2)); cout << "nd2: "; for (int k=0; k<d2.size(); k+ +) cout << d2[k] << ' '; cout << endl; return 0; }
  • 41. 타겟 콘테이너의 뒤에다가 삽입하기 위해 back_iterator는 push_back() 멤버함수를 이용하고 소스 콘테이너의 내용은 변함이 없다. 이 프로그램의 결과는 원래 d2의 내용에 d1의 내용이 추가된 2 4 6 1 3 5 7 9 가 된다. 만일 front_inserter 를 사용한다면 copy (d1.begin(), d1.end(), front_inserter (d2)); push_front() 멤버함수를 이용하여 앞에서부터 삽입해나간다. 결과: 9 7 5 3 1 2 4 6 또한 임의의 위치에 new 아이템을 추가할 수도 있다. 아래는 d2의 처음에 d1을 추가하는 것이다. copy (d1.begin(), d1.end(), inserter(d2, d2.begin())); 결과: 1 3 5 7 9 2 4 6 여기서 주의할 점은 inserter의 두번째 인자는 vector에서는 사용할 수 없다는 것이다. 이유는 vector에는 push_front() 메쏘드가 없기 때문이다. 백터는 무조건 끝에서부터 접근이 가능하다. 4.5.1.3 Stream Iterators 스트림 이터레이터는 화일과 I/O 디바이스(cin이나 cout)를 이터레이터로 다룰수 있는 방법을 제공 한다. 이것은 알고리즘의 인자로 파일과 IO 디바이스를 이용하여 쉽게 처리할 수 있게 해준다. input과 output이터레이터의 주목적이 이러한 스트림 이터레이터 클래스를 지원하는것이다. 알고리즘은 직접 input과 output이터레이터를 사용할수 있게된다. 사실 스트림 이터레이터는 서로 다른 input이나 output을 템플릿화한 것이다. 여기에는 두가지의 스트림이 이터레이터가 있는데 ostream_iterator와 istream_iterator이다.
  • 42. 4.5.1.3.1 The ostream_iterator Class ostream_iterator 오브젝트는 어떤 알고리즘이나 인자로 사용할 수 있다. // outiter.cpp // demonstrates ostream_iterator #include <iostream> #include <algorithm> #include <list> using namespace std; int main() { int arr [] = { 10, 20, 30, 40, 50 }; list <int> theList; for (int j=0; j<5; j+ +) theList.push_back (arr[j]); ostream_iterator<int> ositer(cout, ","); // ostream iterator cout << "nContents of list: "; copy (theList.begin(), theList.end(), ositer); // display list cout << endl; return 0; } 여기서는 int 값을 읽기 위해 ostream iterator를 정의했다. 생성자의 첫번째 인자는 int 값이 씌어질 stream value이고, 두번째 인자 문자열은 각 값마다 덫붙는 문자열이다. 일반적으로 stream value는 화일명이나 cout이면 된다. copy() 알고리즘은 리스트의 내용을 cout으로 복사하게된다. copy()의 세번째 인자가 바로 ostream iterator를 나타낸다. 위의 결과는 다음과 같다. Contents of list: 10, 20, 30, 40, 50,
  • 43. 다음 예제 FOUTITER는 ostream iterator로 화일을 사용하는 것을 보여준다. // outiter.cpp // demonstrates ostream_iterator with files #include <iostream> #include <algorithm> #include <list> using namespace std; int main() { int arr [] = { 11, 21, 31, 41, 51 }; list <int> theList; for (int j=0; j<5; j+ +) theList.push_back (arr[j]); ofstream outfile ("ITER.DAT"); // create file object ostream_iterator<int> ositer(outfile, " "); // ostream iterator cout << "nContents of list: "; copy (theList.begin(), theList.end(), ositer); // display list return 0; } 화일과 연계하기 위해서는 반드시 ofstream을 선언해야한다. 프로그램을 실행하고 메모장으로 ITER.DAT를 열어보면 다음과 같은 결과를 확인할 수 있다. 11 21 31 41 51
  • 44. 4.5.1.3.2 The istream_iterator Class istream_iterator 오브젝트는 input iterator를 가진 어떤 알고리즘이든지 인자로 사용 할 수 있다. 예제 INITER는 이러한 오브젝트가 copy() 알고리즘의 처음 2개의 인자로 사용 되는 걸 보여준다. // initer.cpp // demonstrates istream_iterator #include <iostream> #include <list> #include <algorithm> using namespace std; int main() { list<float> fList (5); // uninitialized list cout << "nEnter 5 floating-point numbers: "; istream_iterator <float> cin_iter(cin); // cin istream_iterator <float> end_of_stream; // eos //K:이곳에서 cin_iter에 값을넣고 하나씩 EOS가 될때까지 fList로 계속 복사한다. copy ( cin_iter, end_of_stream, fList.begin() ); cout << endl; ostream_iterator<float> ositer(cout, "--"); copy (fList.begin(), fList.end(), ositer); cout << endl; return 0; } 위 프로그램의 결과는 Enter 5 floating-point numbers: 1.1 2.2 3.3 4.4 5.5 1.1--2.2--3.3--4.4--5.5--
  • 45. copy()에서 주목할 사항은, 데이터를 cin에서 받기 때문에 복사될 범위의 끝(end_of_stream)을 명시 한다는 것이다. istream_iterator는 cin_iter라고 선언된 cin하고 연결된다. 그렇다면 end_of_stream은 무엇인가? 인자가 없이 생성된 end_of_stream은 이 프로그램에서 특별한 역할을 담당한다. 이것은 항상 스트림의 끝을 나타내는 istream_iterator를 생성한다. user가 데이터를 입력하면서 어떻게 end-of-stream임을 알릴 수 있을까? 그것은 바로 <ctrl><z>를 눌러 입력을 종료시키면 된다. 그리고 위에선, 물론 다른 방법이 많겠지만, 리스트의 내용을 출력하기 위해 ostream_iterator를 사용했다. 다음 예제 FINITER는 copy()의 입력으로 cin대신 file을 사용한 것이다. // finiter.cpp // demonstrates istream_iterator with files #include <iostream> #include <list> #include <fstream> #include <algorithm> using namespace std; int main() { list<int> iList(5); ifstream infile ("ITER.DAT"); // create input file object // (ITER.DAT는 미리 준비를 해야겠죠) istream_iterator<int> file_iter(infile); // file istream_iterator<int> end_of_stream; // eos copy ( file_iter, end_of_stream, iList.begin() ); cout << endl; // dispaly iList ostream_iterator<int> ositer(cout, "--"); copy (iList.begin(), iList.end(), ositer); cout << endl; return 0; } FINITER의 결과는 다음과 같다. 11--21--31--41--51—
  • 46. 이미 존재하는 화일로써 ITER.DAT를 나타내는 ifstream을 사용했다. 위에서 cout대신에 ofstream을 이용하여 outfile로 대치해주면 결과를 화일로도 저장할 수 있다. 그리고 또다른 응용으로써 iList에 데이터를 삽입하기위해 back_inserter를 사용할 수 있다. 방법은 초기화 되지 않은 iList만들고 추가해 나가면 된다. 5 Associative Containers 이제까지 고정된 선형 sequence에 데이터를 저장하는 sequence 콘테이너(vector, list, deque) 를 다루었었다. 유용한 것들이지만, item을 찾기위해서는 콘테이너에 있는 모든 item들을 하나하나 비교해 나감으로써만 가능하다. 반면에 associative 콘테이너에서는 item이 일련의 연속으로 정렬되어 있다. 좀더 복잡한 정렬방식을 사용함으로써 찾고자 하는 item을 빠르게 검색할 수가 있다. 이러한 배열법은 hash table처럼 다른 접근법을 사용하지만, 일반적으로 tree구조를 띈다. 따라서 assoiative 콘테이너의 가장 큰 장점은 검색 속도이다. 검색은 일반적으로 숫자나 문자열같은 single value로 표현되는 key로 이루어진다. 이 키값은 콘테이너에서 object의 속성을 나타내거나, 전체 object를 나타내기도 한다. STL에서 associative 콘테이너의 2가지 주요 분류로 set과 map이 있다. set은 key를 포함한 object를 저장한다. map은 두부분으로(pair) 나누어 저장하는데, 첫번째 부분은 key를 포함한 object이고 두번째 부분은 value를 포함하는 object이다. set과 map은 모두, 각 키에 대해서 오직 하나의 값만 저장하게 된다. 이것은 사전에서 각 단어에 대해서 하나이상의 엔트리를 갖지 못하도록 하는 것과 마찬가지이다. 그러나 STL은 두가지 버전을 가지고 있어서 위의 제약사항이 엄격하게 적용되지는 않는다. multiset, multimap은 set, map과 비슷하지만 같은 키에대해서 여러개의 인스턴스를 가질수 있다. associtave 콘테이너는 다른 콘테이너들과 많은 멤버함수들을 공유하지만, lower_bound()나 equal_range() 같은 몇몇 알고리즘들은 associative 콘테이너에만 있다. 5.1 Sets and Multisets set은 주로 데이터베이스에서 employees같은 사용자정의 클래스를 저장하는데 사용된다. 또한 set은 string같은 단순 object를 저장하는데도 사용된다. 그림 5.5는 set이 데이터를 저장하는 모양새를 나타낸다. object는 순서데로 정렬되어지고, 모두 key를 가진다.
  • 47. set "puma" ┌───────────────┐ ────┐ │ │ │ │ ┌───────┐ │ │ │ │ "cat" │ │ │ │ └───────┘ │ │ │ │ │ │ ┌───────┐ │ │ │ │ "dog" │ │ │ │ └───────┘ │ │ │ │ │ │ ┌───────┐ │ │ │ │ "ocelot" │ │ │ │ └───────┘ │ │ │ │ │ │ ┌───────┐ │ └────┼─▶ │ "puma" │ │ │ └───────┘ │ │ │ │ ┌───────┐ │ │ │ "raindeer" │ │ │ └───────┘ │ │ ▲ │ └────┼──────────┘ │ └─ keys [그림 5.5] A Set of string objects. // set.cpp // set stores string objects #include <iostream> #include <set> #pragma warning (disable: 4786) // for set (microsoft compilers only) #include <string> using namespace std;
  • 48. int main() { string names [] = { "Juanita", "Robert", "Marry", "Amanda", "Marie" }; set <string, less<string> > nameSet (names, names+ 5); set <string, less<string> >::iterator iter; nameSet.insert ("Yvette"); // insert more names nameSet.insert ("Larry"); nameSet.insert ("Robert"); // no effect; already in set nameSet.insert ("Barry"); nameSet.erase ("Marry"); // erase a name cout << "nSize=" << nameSet.size() << endl; iter = nameSet.begin(); while (iter != nameSet.end()) cout << *iter+ + << 'n'; string searchName; cout << "Enter name to search for: "; cin >> searchName; iter = nameSet.find (searchName); if ( iter == nameSet.end() ) cout << "The name " << searchName << " is NOT in the set."; else cout << "The name " << *iter << " IS in the set."; cout << endl; return 0; } set을 선언할 때, 저장될 object(string)의 타입을 명시하고 멤버를 정렬하기위해 function object인 less<> ()를 사용했다. 위의 코드를 보면 지금까지 보아온 STL의 다른 콘테이너들과 유사함을 알 수 있다. array를 이용해서 set을 초기화 하고 insert() 멤버함수를 이용하여 새로운 멤버를 추가하고 반복자(iterator)를 통해 출력을 한다. set에서 특정한 값을 찾기위해서는 find() 멤버함수를 사용한다. (sequential 콘테이너에서는 find() 알고리즘을 사용한다.)
  • 49. 다음은 위의 프로그램을 실행시키고 "George"란 이름을 입력한 결과이다. Size=7 Amanda Barry Juanita Larry Marie Robert Yvette Enter name to search for: George The name George is NOT in set. 그럼 이제 associative 콘테이너에만 있는 멤버함수에 대해서 살펴보도록 하자. 다음의 예제는 앞에서 말한 예제는 lower_bound()와 upper_bound()에 대한 것이다. // setrange.cpp // tests ranges within a set #include <iostream> #include <set> #pragma warning (disable: 4786) // for set (microsoft compilers only) #include <string> using namespace std; int main() { set <string, less<string> > organic; set <string, less<string> >::iterator iter; organic.insert ("Curine"); // insert organic compounds organic.insert ("Xanthine"); organic.insert ("Curarine"); organic.insert ("Melamine"); organic.insert ("Cyanimide"); organic.insert ("Phenol"); organic.insert ("Aphrodine"); organic.insert ("Imidazole"); organic.insert ("Cinchonie"); organic.insert ("Palmitamide");
  • 50. iter = organic.begin(); while ( iter != organic.end() ) cout << *iter+ + << 'n'; string lower, upper; cout << "nEnter range (example C Czz): "; cin >> lower >> upper; iter = organic.lower_bound (lower); while ( iter != organic.upper_bound (upper) ) cout << *iter+ + << 'n'; return 0; } 이 프로그램은 처음에 조직 구성원을 출력한다. 그리고 프롬프트상에서 입력받은 범위에 해당하는 데이터들을 출력한다. Aphrodine Cinchonie Curarine Curine Cyanimide Imidazole Melamine Palmitamide Phenol Xanthine Enter range (example C Czz): Aaa Curb Aphrodine Cinchonine Curarine lower_bound() 멤버함수는 인자로 키값을 받고 키값보다 크거가 같은 값을 갖는 처음 엔트리를 가리키는 iterator를 리턴한다. upper_bound() 멤버함수는 역시 인자로 키값을 받고 키값보다 작거나 같은 값을 갖는 처음 엔트리를 리턴한다.
  • 51. 5.2 Maps and Multimaps map은 key 오브젝트와 value오브젝트의 쌍(pair)으로 이루어진 것을 저장한다. key 오브젝트는 검색에 사용될 key를 포함하는 오브젝트이고, value 오브젝트는 첨부된 데이터이다. key 오브젝트는 set에서 처럼 strings, numbers, 그리고 좀더 복잡한 클래스 오브젝트가 될 수 있다. value는 주로 strings 나 numbers가 되지만 이것 또한 복잡한 클래스오브젝트가 될 수 있다. 예를들어, 어떤 문서가 있을때, key가 word이고 value는 문서에서 이 단어가 쓰인 횟수로 나타낼 수 있다. 이처럼 map은 주로 빈도테이블(frequency table)을 구성하는데 사용된다. 또는 key를 단어(word)로 하고 value를 페이지 번호 리스트로 할 수도 있다. 그림 5.6은 사전처럼 key가 단어이고 value가 설명을 나타내는 것을 도식화 한것이다. "snail" ┌─────────────────────────┐ ────┐ │ │ │ │ ┌───────┐ │ │ │ │ "cat" ├ "A small, furry animal │ │ │ └───────┘ that chases mice." │ │ │ │ │ │ ┌───────┐ │ │ │ │ "dog" ├ "A largre, hairy animal │ │ │ └───────┘ that chases sticks." │ │ │ │ │ │ ┌───────┐ │ └────┼─▶ │ "snail" ├ "A small, shelled animal │ │ └───────┘ that eats gardends." │ │ │ │ ┌───────┐ │ │ │ "puma" ├ "A large, furry animal │ │ └───────┘ that eats hikers." │ │ ▲ │ └────┼────────────────────┘ │ └─ key-value pairs [그림 5.6] A map of word-phrase pairs.
  • 52. map은 Associative Array로써 사용된다. C+ + 배열에서 특정 값을 참고하기 위해 사용되는 배열 인덱스는 integer이다. 즉 anArray[3] 처럼 사용된다. Associative Array은 배열 인덱스 타입을 맘데로 선택할 수 있는 배열 이다. 만약 인덱스 타입을 string으로 했다면 anArray ["jane"]와 같은 표현이 가능하다는 것이다. 5.2.1 An Associative Array 이제 map의 예제로 Associative Array를 사용한 것을 보자. key는 어떤 도시의 이름이 되고 value는 이 도시의 인구를 나타낸다. // asso_arr // demonstrates map used as associative array #include <iostream> #include <string> #include <map> #pragam warning (disable: 4786) // for map (Microsoft only) using namespace std; int main () { string name; int pop; string states [] = { "Wyoming", "Colorado", "Nevada", "Montana", "Arizona", "Idaho" }; int pops [] = {470, 2890, 800, 787, 2781, 944 }; map <string, int, less<string> > mapStates; map <string, int, less<string> >::iterator iter; for (int j=0; j<6; j+ +) { name = states[j]; pop = pops[j]; mapStates[name] = pop; // map에 데이터를 추가한다. } cout << "Enter state: "; cin >> name; pop = mapStates[name]; // 인구자료를 찾는다.
  • 53. cout << "Population: " << pop << ",000n"; cout << endl; for (iter = mapStates.begin(); iter != mapStates.end(); iter+ +) cout << (*iter).first << ' ' << (*iter).second << ",000n"; return 0; } 프로그램을 실행시키고, 도시 이름을 입력하면 프로그램은 map에서 입력한 도시를 키로 해서 인구를 찾아 다시 화면에 출력해주고 전체 자료를 출력한다. Enter state: Wyoming Population: 470,000 Arizona 2718,000 Colorado 2890,000 Idaho 944,000 Montana 787,000 Nevada 800,000 Wyoming 470,000 set이나 map의 검색 속도는 뛰어나다. 이 예제로 속도를 확인하는 것은 어렵지만 수백만건의 데이터를 입력하고 시험해보면 금방 알 수 있다. 위의 예제에서 주목할 것은 map을 선언할때 세번째로 들어간 파라미터이다. map <string, int, less<string> > mapStates; ^^^^ 첫번째 인자는 key 오브젝트의 타입이고, 두번째 인자는 value 오브젝트의 타입이다. 마지막으로 세번째 인자는 키가 저장될때의 정렬방식을 지정한다. 그리고 한가지 더 주의할 것은 map에 데이터를 추가하는 부분이다. mapStates[name] = pop; 위 표현은 C+ + 배열 표현처럼 해당 인덱스에 데이터를 할당하는 것이지만 인덱스 타입은 string이고 배열크기가 정해지지 않았기 때문에 없으면 추가하게 된다.
  • 54. 반대로 값을 참조할때는 아래 처럼 한다. pop = mapStates[name]; 그리고 반복자(iterator)를 사용할 때는 (*iter).first가 key오브젝트이고 (*iter).second가 value 오브젝트이다. 6. Storing User-Defined Objects 이제까지 언급된 예제들은 기본타입으로 존재하는 오브젝트만 다루었지만 STL은 사용자가 작성한 오브젝트 클래스로도 작업할 수 있다. 이번 장에서는 사용자 정의 오브젝트에 대해서 다루어본다. 6.1 A Set of person Objects 이름, 성, 전화번호를 갖는 person 클래스로 시작해보자. 이 클래스를 set에 저장시켜 전화번호 데이터베이스를 만들것이다. // setpers.cpp // uses a multiset to hold person objects #include <iostream> #include <set> #pragma warning (disable:4786) // for set (Microsoft only) #include <string> using namespace std; class person { private: string lastName; string firstName; long phoneNumber; public: person () : lastName ("blank"), firstName ("blank"), phoneNumber (0) { } person (string lana, string fina, long pho) : lastName(lana), firstName (fina), phoneNumber (pho) { }
  • 55. friend bool operator< (const person&, const person&); friend bool operator== (const person&, const person&); void display () const { // display person's data cout << endl << lastName << ",t" << firstName << "ttphone: " << phoneNumber; } }; bool operator < (const person& p1, const person& p2) { if (p1.lastName == p2.lastName) return (p1.firstName < p2.firstName) ? true : false; return (p1.lastName < p2.lastName) ? true : false; } bool operator== (const person& p1, const person& p2) { if (p1.lastName == p2.lastName && p1.firstName == p2.firstName) return true; else return false; } ////////////////////////////////////////////////////////////////////////// int main () { // person 오브젝트 생성 person pers1 ("Deauville", "William", 8435150); person pers2 ("McDonald", "Stacey", 3327536); person pers3 ("Bartoski", "Peter", 6946473); person pers4 ("KuangThu", "Bruce", 4157300); person pers5 ("Wellington", "Jhon", 9207400); person pers6 ("McDonald", "Amanda", 8435150); person pers7 ("Fredericks", "Roger", 7049982); person pers8 ("McDonald", "Stacey", 7764987); multiset < person, less<person> > persSet; // person들의 multiset multiset < person, less<person> >::iterator iter; // iterator
  • 56. // multiset에 person 오브젝트를 추가한다. persSet.insert (pers1); persSet.insert (pers2); persSet.insert (pers3); persSet.insert (pers4); persSet.insert (pers5); persSet.insert (pers6); persSet.insert (pers7); persSet.insert (pers8); cout << "nNumber of entries = " << persSet.size(); // multiset의 content를 출력한다. iter = persSet.begin(); while (iter != persSet.end()) (*iter+ +).display(); // lastName과 firstName을 입력받는다. string searchLastName, searchFirstName; cout << "nnEnter last name of person to search for: "; cin >> searchLastName; cout << "Enter first name: "; cin >> searchFirstName; // 입력받은 이름으로 searchPerson 오브젝트를 생성한다. person searchPerson (searchLastName, searchFirstName, 0); // 해당 이름을 가진 사람이 몇명인가 세어본다. int cntPersons = persSet.count (searchPerson); cout << "Number of persons with this name = " << cntPersons; // 해당 이름을 가진 사람들을 모두 출력한다. iter = persSet.lower_bound (searchPerson); while ( iter != persSet.upper_bound (searchPerson) ) (*iter+ +).display (); cout << endl; return 0; }
  • 57. person 클래스가 STL 콘테이너로 작동되기 위해서는 몇가지 공통으로 쓰이는 멤버함수가 필요하다. 바로 기본생성자, < 오퍼레이터, == 오퍼레이터들이 필요하다. 이 멤버함수들은 리스트 클래스와 여러 알고리즘에서 유용하게 사용된다. 또한 특수한 경우를 위해 필요한 멤버함수를 정의할 수도 있다. 오퍼레이터 < 와 == 는 const 인자이어야 하고 friend로 하는게 좋다 (멤버함수로 해도 상관은 없다). 오버로드된 < 오퍼레이터는 set에 저장될때 정렬방식을 나타낸다. 예제 setpers에서는 last name을 정렬하기위해 사용되었고, last name이 동일하면 first name으로 순서를 정하게 만들었다. 위 프로그램의 결과는 아래와 같다. Number of entries = 8 Bartoski, Peter phone: 6946473 Deauville, William phone: 8435150 Fredericks, Roger phone: 7049982 KuangThu, Bruce phone: 4157300 McDonald, Amanda phone: 8435150 McDonald, Stacey phone: 3327536 McDonald, Stacey phone: 7764987 Wellington, Jhon phone: 9207400 Enter last name of person to search for: McDonald Enter first name: Stacey Number of persons with name = 2 McDonald, Stacey phone: 3327536 McDonald, Stacey phone: 7764987 (K:VC++6.0에서 위의 소스는 friend함수가 클래스의 멤버변수를 사용할 수 없다는 에러가 발생하는대요 알아보니까 VC+ +의 버그라고 합니다. 해결방법은 서비스팩 5를 까시거나 .NET을 사용하시면 됩니다. SP5는 2003/4 현재 아래 주소로부터 받으실 수 있습니다. http://www.microsoft.com/downloads/details.aspx?FamilyID=9066d31d -ba23-4e8a-b7c8- b95f5e54f896&DisplayLang=ko#filelist)
  • 58. 6.2 A List of person Objects 위의 SETPERS 예제에서, set이나 multimap으로 주어진 이름을 검색하는 것은 매우 빠르다. 그렇지만, person 오브젝트를 빠르게 삽입하고 삭제하기를 고려한다면 list의 사용을 고려해야한다. // listpers.cpp // uses a list to hold person objects #include <iostream> #include <list> #include <algorithm> #include <string> using namespace std; class person { private: string lastName; string firstName; long phoneNumber; public: person () : lastName ("blank"), firstName ("blank"), phoneNumber (0L) { } person (string lana, string fina, long pho) : lastName (lana), firstName (fina), phoneNumber (pho) { } friend bool operator < (const person&, const person&); friend bool operator == (const person&, const person&); friend bool operator != (const person&, const person&); friend bool operator > (const person&, const person&); void display () const { // display person's data cout << endl << lastName << ",t" << firstName << "ttphone: " << phoneNumber; } long get_phone () const { return phoneNumber; } };
  • 59. bool operator < (const person& p1, const person& p2) { if (p1.lastName == p2.lastName) return (p1.firstName < p2.firstName) ? true : false; return (p1.lastName < p2.lastName) ? true : false; } bool operator == (const person& p1, const person& p2) { if (p1.lastName == p2.lastName && p1.firstName == p2.firstName) return true; return false; } bool operator != (const person& p1, const person& p2) { return !(p1 == p2); } bool operator > (const person& p1, const person& p2) { return !(p1 < p2) && !(p1 == p2); } int main () { list <person> persList; // list of person list <person>::iterator iter1; // iterator to a list of persons // put persons in list persList.push_back (person("Deauville", "William", 8435150)); persList.push_back (person("McDonald", "Stacey", 3327536)); persList.push_back (person("Bartoski", "Peter", 6946473)); persList.push_back (person("KuangThu", "Bruce", 4157300)); persList.push_back (person("Wellington", "Jhon", 9207400)); persList.push_back (person("McDonald", "Amanda", 8435150)); persList.push_back (person("Fredericks", "Roger", 7049982));
  • 60. persList.push_back (person("McDonald", "Stacey", 7764987)); cout << "nNumber of entries = " << persList.size(); iter1 = persList.begin (); // display contents of list while ( iter1 != persList.end() ) (*iter1++).display(); // find person or person with specified name (last and first) string searchLastName, searchFirstName; cout << "nnEnter last name of person to search for: "; cin >> searchLastName; cout << "Enter first name: "; cin >> searchFirstName; // make a person with that name person searchPerson (searchLastName, searchFirstName, 0L); // search for first match of names iter1 = find (persList.begin(), persList.end(), searchPerson); // find additional matches if ( iter1 != persList.end() ) { cout << "Person(s) with that name is(are)"; do { (*iter1).display (); // display match + +iter1; // search again, one past match iter1 = find (iter1, persList.end(), searchPerson); } while (iter1 != persList.end()); } else cout << "There is no person with that name."; // find person or persons with specified phone number cout << "nnEnter phone number (format 12345678): "; long sNumber; // get search number cin >> sNumber;
  • 61. // iterate through list bool found_one = false; for (iter1=persList.begin(); iter1!=persList.end(); iter1++) { if (sNumber == (*iter1).get_phone()) { // compare numbers if (!found_one) { cout << "Person(s) with that phone number is(are)"; found_one = true; } (*iter1).display(); } } if (!found_one) cout << "There is no person with that phone number"; cout << endl; return 0; } ////////////////////////////////////////////////////////////////////////// 이름으로 검색할때: 위의 예제는 list를 사용하기때문에 lower_bound()/upper_bound() 멤버함수를 사용할 수가 없다. 대신에 검색을 하기위해 find() 알고리즘을 이용하여 list의 모든 멤버를 비교하게된다. 우선 최초검색을 하고, 발견되면 그 다음 위치부터 루프를 돌며 계속 비교를 하게 된다. 즉 find()를 두번 써야한다. Phone Number로 검색할때: find()는 검색하기위해 1차적 검색 특성(primary search characteristic)을 이용하기 때문에 이름으로 검색하는 것보다는 어렵다. 예제에서 전화번호를 찾기위해 brute force 기법을 사용하였다. 이 기법은 리스트 전체를 순회하면서 찾고자 하는 항목을 수동적으로 비교해나간다. if ( sNumber == (*iter).get_phone() ) ... 프로그램은 먼저 모든 엔트리를 출력하고나서 검색할 이름을 묻는다. 입력받은 이름을 찾아서 출력해주고, 다음과정으로 검색할 전화번호를 묻는다. 그리고 다시한번 검색을 하게된다. 결과는 다음과 같다.