STL - Standard Template Library
Autor: Błażej Chodarcewicz
rainbow.mimuw.edu.pl/~bc189380/STL/
Dlaczego STL?
Elastyczność Efektywność Łatwość nauki i prostota Dobra specyfikacja i dokumentacja
Jak to działa?
Containers
Sequence Containers
Tablica C
Dodanie/usunięcie na początku Dodanie/usunięcie z końca Dodanie/usunięcie ze środka Dostęp do pierwszego elementu Dostęp do ostatniego elementu Dostęp do elementu w środku
vector O(n) O(1) O(n) O(1) O(1) O(1)
deque O(1) O(1) O(n) O(1) O(1) O(1)
list O(1) O(1) O(1) O(1) O(1) O(n)
N/a N/a N/a O(1) O(1) O(1)
#include < iostream.h > #include < vector.h > int main () { vector v1; // Empty vector of doubles. v1.push_back (32.1); v1.push_back (40.5); for (int i = 0; i < v1.size (); i++) cout << v1[i] << " "; cout << endl; }
Zobaczymy na ekranie: 32.1 40.5
#include int main (){ deque d; d.push_back(4); d.push_back(9); d.push_back(16); d.push_front(1); for (int i = 0; i < d.size (); i++) cout << "d[" << i << "] = " << d[i] << endl; cout << endl; d.pop_front (); d[2] = 25; for (i = 0; i < d.size (); i++) cout << "d[" << i << "] = " << d[i] << endl; return 0; }
d[0] = 1 d[1] = 4 d[2] = 9 d[3] = 16 d[0] = 4 d[1] = 9 d[2] = 25
#include < iostream.h > #include < list.h > int array1 [] = { 9, 16, 36 }; int array2 [] = { 1, 4 }; int main () { list l1 (array1, array1 + 3); list l2 (array2, array2 + 2); list::iterator i1 = l1.begin (); l1.splice (i1, l2); list< int >::iterator i2 = l1.begin (); while (i2 != l1.end ()) cout << *i2++ << endl; return 0; }
1 4 9 16 36
Adaptacje kontenerów.
Stack i queue można zaimplementować z użyciem trzech podstawowych ciągów. Adaptacja kolekcji dostarcza ograniczony interfejs do kolekcji. Adaptacje nie zawierają iteratorów. Deklaracja: stack > s; stack s; //domyślnie deque
Stack
Najlepiej używać z vector lub deque, można też z list, ale jest to nie najlepszy pomysł bool empty(); size_type size(); value_type& top(); const value_type& top(); void push(const value_type&); void pop();
queue
Najlepiej używać z deque lub list, można też użyć vector’a, ale jest to nie efektywne bool empty(); size_type size(); value_type& front(); const value_type& front(); value_type& back(); const value_type& back(); void push(const value_type&); void pop();
priority_queue
Jako argument bierze typ sekwencyjny oraz funkcję porównującą elementy Najlepiej używać z vector’em lub deque (jeśli rozmiar jest mocno dynamiczny). Nie można używać list, bo niezbędny jest operator[]. Używa implementacji algorytmu kopcowego.
priority_queue interfejs:
bool empty(); size_type size(); value_type& top(); const value_type& top(); void push(const value_type&); void pop();
Kolekcje asocjacyjne.
Uogólnienie kolekcji Najczęściej używane typ kluczy to String Implementacja jest efektywna
Czym różnią się kolekcje asocjacyjne?
Set: zawiera tylko klucze, operacje jak na zbiorze Multiset: jak set tylko, że może być wiele kopii elementów Map: zbiór par klucz, wartość Multimap: klucze mogą się powtarzać
Przykład użycia map
int main(){ map months; months["january"] = 31; months["february"] = 28; months["march"] = 31; months["april"] = 30; months["may"] = 31; months["june"] = 30; months["july"] = 31; months["august"] = 31; months["september"] = 30; months["october"] = 31; months["november"] = 30; months["december"] = 31; }
struct ltstr{ bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } };
Przykład użycia multimap
struct ltstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } }; int main(){ multimap m; m.insert(pair("a", 1)); m.insert(pair("c", 2)); m.insert(pair("b", 3)); m.insert(pair("b", 4));
}
Alokatory
Zawierają funkcje alokacji i dealokacji pamięci Czarne skrzynki
Rodzaje alokatorów.
alloc pthread_alloc
Domyślny alokator. Za zwyczaj ma najlepsze charakterystyki, jest thread-safe. Dla każdego wątku jest oddzielna pula pamięci. Można tego używać tylko jeśli system operacyjny wspiera wielowątkowość. Jest za zwyczaj szybszy od alloc. Problem fragmentacji. thread-safe.
single_client_alloc Alokator dla programów jedno wątkowych. Nie jest
malloc_alloc
Alokator używający standardowej funkcji malloc. Jest thread-safe, ale dość powolny.
Iteratory
Uogólnienie wskaźników Służą do iteracji po elementach Są pośrednikami pomiędzy kolekcjami i algorytmami Możliwość pisania generycznych algorytmów
Rodzaje iteratorów
Input Iterator Output Iterator Forward Iterator Bidirectional Iterator Random Access Iterator Const Iterator
Input Iterator
Output Iterator
Forward Iterator
Biderictional Iterator
Random Access Iterator
Algorytmy STL
Generyczne algorytmy oddzielają algorytm od danych „Nie zależą” od reprezentacji danych Operują na iteratorach
Non-mutating
template UnaryFunction for_each(InputIterator first, InputIterator last, UnaryFunction f); template iterator_traits::difference_type count(InputIterator first, InputIterator last, const EqualityComparable& value); template InputIterator find(InputIterator first, InputIterator last, const EqualityComparable& value) template bool equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2); template ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2);
Przykład:
int main() { int A[] = { 2, 0, 4, 6, 0, 3, 1, -7 }; const int N = sizeof(A) / sizeof(int); cout << "Number of zeros: " << count(A, A + N, 0) << endl; }
Przykład:
template struct print : public unary_function{ print(ostream& out) : os(out), count(0) {} void operator() (T x) { os << x << ' '; ++count; } ostream& os; int count; }; int main(){ int A[] = {1, 4, 2, 8, 5, 7}; const int N = sizeof(A) / sizeof(int); print P = for_each(A, A + N, print(cout)); cout << endl << P.count << " objects printed." << endl; }
Mutating
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result); void swap(Assignable& a, Assignable& b); ForwardIterator2 swap_ranges(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2); OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryFunction op); void replace(ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value) void fill(ForwardIterator first, ForwardIterator last, const T& value); ForwardIterator remove(ForwardIterator first, ForwardIterator last, const T& value); void reverse(BidirectionalIterator first, BidirectionalIterator last); OutputIterator remove_copy(InputIterator first, InputIterator last, OutputIterator result, const T& value);
Przykład:
vector V(5); iota(V.begin(), V.end(), 1); list L(V.size()); copy(V.begin(), V.end(), L.begin()); assert(equal(V.begin(), V.end(), L.begin()));
Algorytmy sortowania:
void sort(RandomAccessIterator first, RandomAccessIterator last); void stable_sort(RandomAccessIterator first, RandomAccessIterator last); bool is_sorted(ForwardIterator first, ForwardIterator last, StrictWeakOrdering comp) OutputIterator merge(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result); bool binary_search(ForwardIterator first, ForwardIterator last, const LessThanComparable& value); ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last, const LessThanComparable& value);
Przykład:
int A[] = {1, 4, 2, 8, 5, 7}; const int N = sizeof(A) / sizeof(int); sort(A, A + N); copy(A, A + N, ostream_iterator(cout, " "));
wynik: " 1 2 4 5 7 8"
Algorytmy operujące na zbiorach
bool includes(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2); OutputIterator set_union(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result); OutputIterator set_intersection(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result); OutputIterator set_difference(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result);
Przykład:
inline bool lt_nocase(char c1, char c2) { return tolower(c1) < tolower(c2); }
int main() { int A1[] = {1, 3, 5, 7, 9, 11}; int A2[] = {1, 1, 2, 3, 5, 8, 13}; char A3[] = {'a', 'b', 'b', 'B', 'B', 'f', 'h', 'H'}; char A4[] = {'A', 'B', 'B', 'C', 'D', 'F', 'F', 'H' };
const int N1 = sizeof(A1) / sizeof(int); const int N2 = sizeof(A2) / sizeof(int); const int N3 = sizeof(A3); const int N4 = sizeof(A4);
Wynik: Intersection of A1 and A2: 1 3 5 Intersection of A3 and A4: a b b f h
cout << "Intersection of A1 and A2: "; set_intersection(A1, A1 + N1, A2, A2 + N2, ostream_iterator(cout, " ")); cout << endl << "Intersection of A3 and A4: "; set_intersection(A3, A3 + N3, A4, A4 + N4, ostream_iterator(cout, " "), lt_nocase); cout << endl; }
Algorytmy kopcowe
void push_heap(RandomAccessIterator first, RandomAccessIterator last); void pop_heap(RandomAccessIterator first, RandomAccessIterator last); void make_heap(RandomAccessIterator first, RandomAccessIterator last); void sort_heap(RandomAccessIterator first, RandomAccessIterator last); bool is_heap(RandomAccessIterator first, RandomAccessIterator last);
Przykład:
int main(){ int A[] = {1, 2, 3, 4, 5, 6}; const int N = sizeof(A) / sizeof(int); make_heap(A, A+N); cout << "Before pop: "; copy(A, A+N, ostream_iterator(cout, " ")); pop_heap(A, A+N); cout << endl << "After pop: "; copy(A, A+N-1, ostream_iterator(cout, " ")); cout << endl << "A[N-1] = " << A[N-1] << endl; } Wynik: Before pop: 6 5 3 4 2 1 After pop: 5 4 3 1 2 A[N-1] = 6
Obiekty funkcyjne (funktory)
Obiekty, które mogą być wołana jak funkcje Zwykłe funkcje to też obiekty funkcyjne Operator () Modele: Generator, Unary Function, Binary Function Predicate, Binary Predicate Adaptacyjne obiekty funkcyjne
Przykłady:
vector V(100); generate(V.begin(), V.end(), rand);
struct less_mag : public binary_function { bool operator()(double x, double y) { return fabs(x) < fabs(y); } }; vector V; ... sort(V.begin(), V.end(), less_mag());
Przykład:
struct adder : public unary_function { adder() : sum(0) {} double sum; void operator()(double x) { sum += x; } };
vector V; ... adder result = for_each(V.begin(), V.end(), adder()); cout << "The sum is " << result.sum << endl;
Przykład:
list L; ... list::iterator new_end = remove_if(L.begin(), L.end(), compose2(logical_and(), bind2nd(greater(), 100), bind2nd(less(), 1000))); L.erase(new_end, L.end());
Gdzie szukać informacji?
http://www.sgi.com/tech/stl/ - implementacja STL firmy Silicon Graphics, Inc. (SGI) http://www.informatik.hs-bremen.de/~brey/stlbe.html - książka "Designing Components with the C++ STL" http://www.xraylith.wisc.edu/~khan/software/stl/STL.newbie.html - strona o STL z 1995 roku http://www.cs.brown.edu/people/jak/proglang/cpp/stltut/tut.html prosty tutorial http://www.cs.rpi.edu/~wiseb/xrds/ovp2-3b.html - krótki opis STL'a http://pages.cpsc.ucalgary.ca/~kremer/STL/1024x768/index.html strona o STL'u
Dziękuję za uwagę!