2.78k likes | 3.16k Views
Standard C++ Library. 목 차 (1/2). Introduction 반복자 (Iterator) 함수 (function) 와 조건자 (predicate) 컨테이너 (container) 클래스 vector 와 vector<bool> list deque set, multiset, bitset. 목 차 (2/2). map 과 multimap stack 과 queue priority_queue string Generic 알고리즘 예외 처리 (exception handling)
E N D
목 차(1/2) • Introduction • 반복자(Iterator) • 함수(function)와 조건자(predicate) • 컨테이너(container) 클래스 • vector와 vector<bool> • list • deque • set, multiset, bitset
목 차(2/2) • map과 multimap • stack과 queue • priority_queue • string • Generic 알고리즘 • 예외 처리(exception handling) • complex
목 차 • 표준 C++ 라이브러리(standard C++ library) • 다른 라이브러리와의 차이점 • 비 객체지향설계의 장단점 • STL의 구조 • STL 맛보기
표준 C++ 라이브러리(1/2) • 국제 표준 기구(International Standards Organization, ISO)와 미국 국가 표준 기관(American National Standards Institute, ANSI)은 C++ 프로그래밍 언어의 표준화 작업을 마침(표준 번호: ISO/IEC 14882) • 이 표준화 과정에서 가장 중요한 부분의 하나가 바로 「표준 C++ 라이브러리(standard C++ library)」이며, 이 라이브러리는 많은 양의 클래스와 함수들을 제공함
표준 C++ 라이브러리(2/2) • 표준 C++ 라이브러리는 다음을 포함하고 있음 • 많은 양의 데이터 구조와 알고리즘. 특히 이 부분만 따로 「표준 템플릿 라이브러리(standard template library, STL)」라고 부른다. • 입출력 스트림 • locale 기능 • string템플릿 클래스 • complex템플릿 클래스 • numeric_limits템플릿 클래스 • 메모리 관리 기능 • Language support 기능 • 예외 처리(exception handling) 기능 • 수치 배열용으로 최적화된 valarray클래스
다른 라이브러리와의 차이점 • STL의 구조와 설계는 대부분의 다른 C++ 라이브러리와는 거의 모든 면에서 완전히 다르다. • STL은 캡슐화(encapsulation)를 피하고 있고, 상속(inheritance)을 거의 사용하고 있지 않다 • STL의 설계자는 객체지향 방법을 피했으며, 공통된 데이터 구조를 사용하여 수행하는 작업들을 데이터구조의 표현과 분리하였음 • STL을 '알고리즘의 집합'과 이들 알고리즘을 사용하여 다루는 '데이터 구조들의 집합'으로 보는 것이 적절함
비 객체지향설계의 장단점(1/2) • 장점 • 소스코드 크기의 축소 • STL에는 약 50여 개의 다양한 알고리즘과 10여 개의 주요 데이터 구조들이 서로 분리되어 있어서, 소스코드의 크기를 줄일 수 있음 • 만약 이렇게 분리를 하지 않는다면, 서로 다른 데이터 구조 각각에 대해 모든 알고리즘들을 또 구현해야 하며, 결국 수백 개의 멤버함수를 필요로 하게 됨 • 유연성 • STL의 알고리즘들이 기존의 C++ 포인터와 배열에도 사용될 수 있음
비 객체지향설계의 장단점(2/2) • 단점 • 반복자(iterator): 불일치(mismatch)와 무효화(invalidation) • 반복자들이 같은 컨테이너로부터 온 것인지를 증명하기가 불가능 함. 한 컨테이너의 시작 반복자를 다른 컨테이너의 끝 반복자와 같이 사용하게 되면 어떤 일이 일어나게 될 지 장담할 수 없게 됨 • 반복자는 자신과 연관된 컨테이너에 대해 삽입이나 반복을 수행한 뒤에, 무효화 될 수도 있음. 이렇게 무효화된 반복자를 검사도 않고 사용하게 되면 예상치 못한 결과를 초래할 수 있음
STL의 구조 • 라이브러리를 이와 같은 구조로 만든다면 소프트웨어 설계 작업량을 상당히 줄일 수 있음 • 라이브러리가 제공하는 요소들을 사용자가 만든 요소들과 같이 사용할 수 있음
STL 맛보기(1/3) #include <stdlib.h> #include <iostream.h> // qsort()의 인자로 쓰일 비교함수 inline int cmp(const void *a, const void *b) { int aa = *(int *)a; int bb = *(int *)b; return (aa < bb) ? -1 : (aa > bb) ? 1 : 0; } int main() { const int size = 1e5; int array[size]; // 100,000개의 정수로 이루어진 배열 // 입력 int n = 0; while (cin >> array[n]) n++; n--; // 정렬 qsort(array, n, sizeof(int), cmp); // 출력 for (int i = 0; i < n; i++) cout << array[i] << endl; } 프로그램 1: STL을 전혀 사용하지 않음
STL 맛보기(2/3) #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; // 입력 int input; while (cin >> input) v.push_back(input); // 정렬 sort(v.begin(), v.end()); // 출력 for (int i = 0; i < v.size(); i++) cout << v[i] << endl; } 프로그램 2: 컨테이너, 반복자, 알고리즘
STL 맛보기(3/3) #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; istream_iterator<int> start(cin), end; back_insert_iterator<vector<int>> dest(v); // 입력 copy(start, end, dest); // 정렬 sort(v.begin(), v.end()); // 출력 copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\n")); } 프로그램 3: 반복자 어댑터
목 차 • 반복자에 대한 소개 • 반복자의 종류 • 스트림 반복자 • 삽입 반복자 • 반복자 연산
반복자에 대한 소개(1/5) • 반복자(Iterator) • 포인터와 상당히 비슷하며, 컨테이너에 저장되어 있는 원소들을 참조할 때 사용함 • 알고리즘마다 각기 다른 방식으로 컨테이너를 훑어가기 때문에, 반복자에도 여러 가지 종류가 있게 됨 • 컨테이너와 알고리즘 사이에 매개체 역할을 함 • 프로그래머는 표준 라이브러리에서 제공하는 컨테이너들에 알맞은 반복자를 생성할 수 있음
반복자에 대한 소개(2/5) • 구간(Range) • 구간(range)이란 컨테이너에 담긴 값들의 시퀀스를 나타냄 • 구간은 반복자 한 쌍으로 나타내며, 이 반복자들이 각각 시퀀스의 시작과 끝을 가리킴 • 이 두개의 반복자는 같은 컨테이너로부터 생성된 것이어야 하며 두번째 반복자는 첫번째 반복자 이후에 와야 함 • 널 포인터를 참조하는 것이 논리적인 에러를 유발할 수 있듯이, 어떤 값도 가리키지 않는 반복자를 참조하는 것은 에러를 유발함
반복자에 대한 소개(3/5) • 구간(Range) • 두번째 반복자가 가리키는 원소는 구간의 일부로 인정되지 않음(past-the-end 원소라고 하며, 구간의 마지막 원소 다음에 위치한 원소를 지칭) • 범위의 끝을 나타내는 반복자(두번째 반복자)는 아무 의미가 없는 것을 가리키고 있으므로 참조하는 것은 피해야 함 • end()는 끝이 아니다? • end()가 가리키는 것은 컨테이너의 맨 마지막 원소가 아니라는 점에 항상 주의 • end()가 가리키고 있는 것은 맨 마지막 원소의 바로 다음번 원소이다 (past-the-end 반복자)
반복자에 대한 소개(4/5) • end()는 끝이 아니다? • 아무 원소를 가지고 있지 않은 컨테이너의 begin()과 end()는 같아짐 • 기존의 포인터와 같이, 반복자를 수정하는 기본적인 연산은 증가 연산자(++)이다 • 반복자 i에 유한번의 증가 연산자를 적용하여, 반복자 i가 j와 같아질 수 있다면, 반복자 j는 반복자 i로부터 도달가능 (Reachable)하다고 함 bool empty(const STL_Container& container) { return container.begin() == container.end(); }
반복자에 대한 소개(5/5) • 반복자 구간(iterator range) • 반복자 2개를 사용하여 컨테이너의 특정 구간에 속한 원소들을 나타내고자 한다면, 두번째 반복자가 첫번째 반복자로부터 도달가능(reachable)해야 함 • 그렇지 않으면, 에러가 발생한다. 이는 알고리즘이나 컨테이너 내부에서 검사하지 않기 때문에, 프로그래머가 책임져야 할 부분이다
반복자의 종류(1/3) • 입력 반복자(input iterator) • 읽기만 가능, 순방향 이동 • 출력 반복자(output iterator) • 쓰기만 가능, 순방향 이동 • 순방향 반복자(forward iterator) • 읽기/쓰기 모두 가능, 순방향 이동 • 양방향 반복자(bidirectional iterator) • 읽기/쓰기 모두 가능, 순방향/역방향 이동 • 임의 접근 반복자(random access iterator) • 읽기/쓰기 모두 가능, 임의접근
반복자의 종류(2/3) • 반복자는 계층적으로 분류된다 • 순방향 반복자는 입력 반복자와 출력 반복자를 필요로 하는 곳이라면 언제든지 사용될 수 있음 • 양방향 반복자는 순방향 반복자가 올 수 있는 자리에 올 수 있음 • 양방향 반복자를 필요로 하는 상황에서는 임의접근 반복자를 사용할 수 있음
반복자 종류 생성되는 방식 읽기 접근 쓰기 증감 비교 입력 반복자(input iterator) istream_iterator =*p -> ++ == != 출력 반복자(output iterator) ostream_iteratorinserterfront_inserterback_inserter *p= ++ 순방향 반복자(forward iterator) =*p -> *p= ++ == != 양방향 반복자(bidirectional iterator) list set과 multiset map과 multimap =*p -> *p= ++ -- == != 임의접근 반복자(random access iterator) 일반 포인터 vector deque =*p ->[] *p= ++ --+ - += -= == !=< > <= >= 반복자의 종류(3/3)
입력 반복자(input iterator)(1/3) • 가장 단순한 형태의 반복자이다 • 위 알고리즘은 입력 반복자가 갖춰야 할 세가지 요구사항을 설명 • 반복자는 다른 반복자와의 상등여부를 비교할 수 있어야 함 • 반복자는 * 연산자를 사용하여 반복자가 가리키는 값을 얻을 수 있어야 함 • 반복자는 ++ 연산자를 사용하여 다음 원소를 가리킬 수 있도록 증가될 수 있어야 함 template <class InputIterator, class T> InputIterator find (InputIterator first, InputIterator last, const T& value) { while (first != last && *first != value) ++first; return first; }
입력 반복자(input iterator)(2/3) • 세가지 종류의 입력 반복자가 존재 • 일반 포인터 • 일반 포인터를 참조하고 증가시킬 수 있고, 또한 임의의 값을 접근할 수 있으므로 일반 포인터는 입/출력 반복자, 또는 임의접근 반복자로 사용될 수 있음 • 표준 라이브러리의 generic 알고리즘은 표준 라이브러리에서 제공되는 컨테이너뿐만 아니라 C++ 배열에도 사용될 수 있다 int data[100]; ... int *where = find(data, data + 100, 7);
입력 반복자(input iterator)(3/3) • 세가지 종류의 입력 반복자가 존재 • 컨테이너 반복자(container iterator) • 표준 라이브러리가 제공하는 다양한 컨테이너로부터 생성된 반복자들은 모두 입력 반복자가 갖추어야 할 조건을 만족 (begin(), end()) • 위 예제에서 iterator 대신, const_iterator를 사용하여 선언하면, 그 반복자는 상수 반복자가 된다 • 입력 스트림 반복자(input stream iterator) • 표준 라이브러리는 입력 반복자를 사용하여 입력 스트림에 대해 작업을 할 수 있는 방법을 제공 • istream_iterator클래스를 통해 제공 vector<int>::iterator where = find(v.begin(), v.end(), 7);
출력 반복자(output iterator)(1/2) • 시퀀스에 값을 대입하는데 사용될 수 있지만, 값을 접근하는데는 사용될 수 없다 • 병렬 시퀀스 • 많은 수의 알고리즘들이 두개의 병렬 시퀀스를 다룸 • 두 번째 시퀀스는 반복자 한쌍 대신 시작 반복자 한개만을 사용하여 표시 • 두 번째 시퀀스가 적어도 첫번째 시퀀스만큼의 원소 갯수를 가지고 있다고 가정 template <class InputIterator, class OutputIterator> OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result) { while (first != last) *result++ = *first++; return result; }
출력 반복자(output iterator)(2/2) • 4가지 종류의 출력 반복자가 존재 • 일반적인 포인터 • 표준 라이브러리의 컨테이너가 생성한 모든 반복자 • 출력 스트림 반복자 • 삽입 반복자 • 컨테이너에 대한 삽입연산 int data[100]; vector<int> newdata(100); ... copy (data, data+100, newdata.begin());
순방향 반복자(forward iterator) • 값을 접근하고 갱신하는 것이 가능 • 일반적인 포인터는 순방향 반복자로 사용될 수 있다 template <class ForwardIterator, class T> void replace (ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value) { while (first != last) { if (*first == old_value) *first = new_value; ++first; } } replace(aVec.begin(), aVec.end(), 7, 11);
양방향 반복자(bidirectional iterator) (1/2) • 감소 연산자(--)를 지원하므로, 컨테이너내의 원소들을 순방향이나 역방향으로 이동하는 것이 허용 template <class BidirectionalIterator, class OutputIterator> OutputIterator reverse_copy (BidirectionalIterator first, BidirectionalIterator last, OutputIterator result) { while (first != last) *result++ = *--last; return result; } list<int> aList; .... vector<int> aVec(aList.size()); reverse_copy(aList.begin(), aList.end(), aVec.begin());
양방향 반복자(bidirectional iterator) (2/2) • 일반적인 포인터는 양방향 반복자로 사용될 수 있다
임의접근 반복자(random access iterator) (1/2) • 기존의 일반 포인터가 했던 모든 것을 할 수 있다 • 첨자에 의한 접근이 가능 • 다른 반복자와의 차(두 반복자 사이에 존재하는 원소의 수)를 구할 수 있음 • 포인터에 대한 산술연산 가능 • 예: x+10 (x부터 10개 원소 이후의 위치를 나타냄) • 정렬과 이진검색과 같은 알고리즘을 수행하는데 주로 사용
임의접근 반복자(random access iterator) (2/2) template <class RandomAccessIterator> void mixup(RandomAccessIterator first, RandomAccessIterator last) { while (first < last) { iter_swap(first, first + randomInteger(last - first)); ++first; } } // n보다 작고 0보다 크거나 작은 정수형 난수를 리턴 unsigned int randomInteger(unsigned int n) { return rand() % n; } • 일반 포인터는 임의 접근 반복자로 사용될 수 있음
역 반복자(reverse iterator) (1/2) • 표준 반복자가 부여 받은 순서와는 반대되는 순서로 값들을 접근한다 • vector나 list에서 역 반복자는 마지막 원소를 맨 먼저 접근하고, 첫번째 원소를 맨 마지막에 접근 • set에서는 가장 큰 원소가 맨 먼저 얻어지고, 가장 작은 원소가 마지막에 접근 • list, set, map 타입은 역 양방향 반복자를 생성하는 멤버 함수를 한 쌍 가지고 제공 • rbegin()과 rend()함수는 해당 컨테이너를 역순으로 훑는 반복자를 생성한다 • 역 반복자에 대한 증가연산은 후퇴를 의미하고, 감소연산은 전진을 의미
역 반복자(reverse iterator) (2/2) • vector와 deque 타입에서는 역 임의접근 반복자를 생성하는 멤버 함수가 제공 • rbegin()과 rend() • 증가연산,덧셈 연산 역시 후퇴를 의미
스트림 반복자(stream iterator) • 스트림 반복자는 반복자 연산을 이용하여 입력 또는 출력 스트림을 접근하는데 사용 • 두 가지 종류 • 입력 스트림 반복자 • 출력 스트림 반복자
입력 스트림 반복자 (1/3) • 반복자 연산을 통해 입력 스트림으로부터의 read 작업을 수행 • istream_iterator객체를 생성하여 수행 • istream_iterator의 생성자로 들어가는 인자는 접근할 스트림을 나타냄 • 입력 스트림 반복자에 대해 증가 연산자(++)가 적용될 때마다 스트림으로부터 새로운 값을 읽어(>> 연산자 사용) 저장함 • 이 값은 참조 연산자(*)를 사용하여 얻을 수 있음 • 생성자에 아무런 인자도 넣지 않고 istream_iterator에 의해 생성된 객체는 입력의 끝을 나타내는 반복자로 사용됨
입력 스트림 반복자 (2/3) • 입력 스트림 반복자는 입력 반복자이기 때문에, 원소들에 대한 접근만이 가능 • 원소들은 오직 한번 순방향으로만 접근이 가능
입력스트림 반복자 (3/3) #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; istream_iterator<int> intstream(cin), eof; while(intstream != eof) v.push_back(*intstream++); for(int i=0;i < v.size();i++) cout << v[i] << endl; } #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; istream_iterator<int> intstream(cin), eof; back_insert_iterator<vector<int> > dest(v); copy(intstream, eof, dest); for(int i=0;i < v.size();i++) cout << v[i] << endl; }
출력 스트림 반복자 (1/2) • 반복자 연산이 수행될 때 출력 스트림으로 write 작업이 수행됨 • 값들이 반복자로 대입될 때마다, 내부적으로 << 연산자를 통해 해당 출력 스트림으로 출력됨 • 출력 스트림 반복자를 생성하기 위해서는 생성자의 인자로 출력 스트림을 지정해야 함 • 생성자의 생략 가능한 두번째 인자는 출력되는 값들 사이의 분리자로 사용될 문자열을 나타냄
출력 스트림 반복자 (2/2) #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; istream_iterator<int> intstream(cin), eof; ostream_iterator<int> outstream(cout,"\n"); while(intstream != eof) v.push_back(*intstream++); for(int i=0;i < v.size();i++) *outstream++ = v[i]; } #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; istream_iterator<int> intstream(cin), eof; ostream_iterator<int> outstream(cout,"\n"); back_insert_iterator<vector<int> > dest(v); copy(intstream, eof, dest); copy(v.begin(),v.end(),outstream); }
삽입 반복자 (1/4) • 출력 반복자에 의해 참조되는 값에 대입을 하면, 그 위치의 내용을 덮어쓰게 됨 vector<int> a(10); … list<int> c; ... copy(a.begin(), a.end(), c.begin());
삽입 반복자 (2/4) • 삽입 반복자라고 불리는 일종의 어댑터를 사용하여 copy()와 같은 알고리즘을 사용할 때 원소들을 덮어쓰지 않고, 해당 컨테이너로 삽입할 수 있음 vector<int> a(10); … list<int> d; … copy(a.begin(), a.end(), front_inserter(d)); vector의 원소들을 list에 삽입하는 코드
삽입 반복자 (3/4) • 3가지 종류의 삽입 반복자가 존재 • front_inserter() • 해당 컨테이너의 앞쪽에 값들을 삽입 • list와 deque 컨테이너 클래스에서 사용 가능 • back_inserter() • 해당 컨테이너의 뒤쪽에 값들을 삽입 • list, deque, set, vector 등의 컨테이너 클래스에서 사용 가능
삽입 반복자 (4/4) • 3가지 종류의 삽입 반복자가 존재 • inserter() • 가장 일반적인 형태의 삽입 반복자 • Inserter의 생성자는 두개의 인자를 취함(컨테이너, 컨테이너내의 반복자) • 원소들을 컨테이너의 특정 위치로 복사(list의 경우, 원소들이 지정된 위치의 바로 앞쪽에 복사됨) • list, deque, set, vector, map, stack 등의 모든 컨테이너 클래스에서 사용 가능
#include <iostream> #include <vector> #include <list> #include <algorithm> using namespace std; int main() { int threeToOne[] = {3, 2, 1}; int fourToSix[] = {4, 5, 6}; int sevenToNine[] = {7, 8, 9}; list<int> aList; // first insert into the front // note that each value becomes new front copy(threeToOne, threeToOne+3, front_inserter(aList)); // then insert into the back copy(sevenToNine, sevenToNine+3, back_inserter(aList)); // find the seven, and insert into middle list<int>::iterator seven = find(aList.begin(), aList.end(), 7); copy(fourToSix, fourToSix+3, inserter(aList, seven)); // copy result to output copy(aList.begin(), aList.end(), ostream_iterator<int>(cout, " ")); cout << endl; } 실행 결과: 1 2 3 4 5 6 7 8 9
Traits class(1/2) template <class Iterator> struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; };
Traits class(2/2) template <class T> struct iterator_traits<T*> { typedef ptrdiff_t difference_type; // ptrdiff_t : stddef.h typedef T value_type; typedef T* pointer; typedef T& reference; typedef random_access_iterator_tag iterator_category; };
Tag classes struct input_iterator_tag {}; struct output_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag {}; struct bidirectoional_iterator_tag : public forward_iterator_tag {}; struct random_access_iterator_tag : public bidirectional_iterator_tag {};