STL
Transkrypt
STL
Wstęp do programowania obiektowego STL - Standard Template Library 1 STL – z ang. Standard Template Library, (pol. standardowa biblioteka szablonów) – biblioteka C++ zawierająca pojemniki, iteratory, algorytmy, oraz inne konstrukcje w formie szablonów, do używania w programach. 2 Najważniejsze pojęcia STL Pojemniki – ugrupowania elementów takich samych typów. Najprostszym pojemnikiem jest (nam znana) tablica. Iteratory – konstrukcje umożliwiające (najczęściej sekwencyjny) dostęp do elementów pojemnika Algorytmy generyczne – algorytmy przetwarzające dane w pojemnikach (często z użyciem iteratorów) 3 Jak to działa? 4 Dlaczego STL? Elastyczność Efektywność Dobra specyfikacja i dokumentacja 5 STL Containers (zbiorniki \ kontenery) 6 Zbiorniki sekwencyjne i operacje na nich tablica (standardowa) vector deque list Dodanie/usunięcie na początku brak O(n) O(1) O(1) Dodanie/usunięcie z końca brak O(1) O(1) O(1) Dodanie/usunięcie ze środka brak O(n) O(n) O(1) Dostęp do pierwszego elementu O(1) O(1) O(1) O(1) Dostęp do ostatniego elementu O(1) O(1) O(1) O(1) Dostęp do elementu w środku O(1) O(1) O(1) O(n) 7 Zbiornik vector #include < iostream.h > #include < vector.h > int main () { vector <double> 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 8 #include <deque.h> int main (){ deque<int> 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 9 #include < iostream.h > #include < list.h > int array1 [] = { 9, 16, 36 }; int array2 [] = { 1, 4 }; int main () { list<int> l1 (array1, array1 + 3); list<int> l2 (array2, array2 + 2); list<int>::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 10 Adaptacje kontenerów. Stos i kolejkę można zaimplementować z użyciem trzech podstawowych kontenerów sekwencyjnych (vector, deque, list). Adaptacja kolekcji dostarcza ograniczony interfejs do kolekcji. Adaptacje nie zawierają iteratorów. Deklaracje: stack <vector<int >> s; stack <int> s; //domyślnie deque<T> 11 Stack (stos) Najlepiej używać z vector lub deque, można też z list, ale jest to niepolecane. Operacje: bool empty(); size_type size(); const value_type& top(); void push(const value_type&); void pop(); 12 Queue (kolejka) Najlepiej używać z deque lub list, można też użyć vector’a, ale jest to nieefektywne (do kolejki powinniśmy mieć dostęp z obu stron). Operacje: 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(); 13 priority_queue (kolejka priorytetowa) Wstawianie odbywa się na miejscu określonym przez priorytet elementu 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 [] (indeksowanie). Używa implementacji algorytmu kopcowego. 14 Metody priority_queue: bool empty(); size_type size(); value_type& top(); const value_type& top(); void push(const value_type&); void pop(); 15 Kolekcje asocjacyjne Uogólnienie kolekcji Najczęściej używane typ kluczy to string (napis) Efektywna implementacja 16 Czym różnią się między sobą kolekcje asocjacyjne? Set: zawiera tylko klucze, operacje jak na zbiorze Multiset: jak set, tyle że może być wiele kopii kluczy Map: zbiór par (klucz, wartość) Multimap: klucze mogą się powtarzać 17 Przykład użycia map int main(){ map<const char*, int, ltstr> 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; } }; } 18 Przykład użycia multimap struct ltstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } }; int main(){ multimap<const char*, int, ltstr> m; m.insert(pair<const char* const, int>("a", 1)); m.insert(pair<const char* const, int>("c", 2)); m.insert(pair<const char* const, int>("b", 3)); m.insert(pair<const char* const, int>("b", 4)); } 19 Alokatory Zawierają funkcje alokacji i dealokacji pamięci Czarne skrzynki 20 Rodzaje alokatorów. alloc Domyślny alokator. Zazwyczaj ma najlepsze charakterystyki, jest thread-safe. pthread_alloc Dla każdego wątku jest oddzielna pula pamięci. Można tego używać wyłącznie jeśli system operacyjny wspiera wielowątkowość. Jest zazwyczaj szybszy od alloc. Problem fragmentacji. single_client_alloc Alokator dla programów jedno wątkowych. Nie jest thread-safe. malloc_alloc Alokator używający standardowej funkcji malloc. Jest thread-safe, ale dość powolny. 21 Iteratory Obiektowe 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 Bardzo przydatna jest arytmetyka wskaźników (wyliczanie adresu za pomocą przesunięcia, różnicy adresów, itp.) 22 Rodzaje iteratorów Input Iterator Output Iterator Forward Iterator Bidirectional Iterator Random Access Iterator Const Iterator 23 Input Iterator 24 Output Iterator 25 Forward Iterator 26 Bidirectional Iterator 27 Random Access Iterator 28 Algorytmy STL Generyczne algorytmy oddzielają algorytm od danych „Nie zależą” od reprezentacji danych Operują na iteratorach 29 Non-mutating (nie zmieniające zawartości pojemnika) template <class InputIterator, class UnaryFunction> UnaryFunction for_each (InputIterator first, InputIterator last, UnaryFunction f); template <class InputIterator, class EqualityComparable> iterator_traits<InputIterator>::difference_type count (InputIterator first, InputIterator last, const EqualityComparable& value); template<class InputIterator, class EqualityComparable> InputIterator find (InputIterator first, InputIterator last, const EqualityComparable& value) template <class InputIterator1, class InputIterator2> bool equal (InputIterator1 first1, InputIterator1 last1, InputIterator2 first2); template <class ForwardIterator1, class ForwardIterator2> ForwardIterator1 search (ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2); 30 Przykład (zliczanie wystąpień zera): 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; } template<class InputIterator, class EqualityComparable> InputIterator find (InputIterator first, InputIterator last, const EqualityComparable& value) 31 Przykład (wypisanie każdego elementu tablicy): template<class T> struct print : public unary_function<T, void>{ 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<int> P = for_each(A, A + N, print<int>(cout)); cout << endl << P.count << " objects printed." << endl; } 32 Mutating (zmieniające zawartość pojemnika) 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); 33 Przykład (kopiowanie zawartości vectora do listy): vector<int> V(5); iota(V.begin(), V.end(), 1); // utworzenie vectora V //wypełnienie rosnącymi wartościami list<int> L(V.size()); copy(V.begin(), V.end(), L.begin()); assert(equal(V.begin(), V.end(), L.begin())); 34 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); 35 Przykład (sortowanie tablicy): 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<int>(cout, " ")); wynik: " 1 2 4 5 7 8" 36 Algorytmy operujące na zbiorach (zawieranie, suma, część wspólna, różnica) 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); 37 Przykład (znajdowanie części wspólnej): 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' }; Wynik: Intersection of A1 and A2: 1 3 5 Intersection of A3 and A4: a b b 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); cout << "Intersection of A1 and A2: "; set_intersection(A1, A1 + N1, A2, A2 + N2, ostream_iterator<int>(cout, " ")); cout << endl << "Intersection of A3 and A4: "; set_intersection(A3, A3 + N3, A4, A4 + N4, ostream_iterator<char>(cout, " "), lt_nocase); cout << endl; } 38 Algorytmy kopcowe void push_heap (RandomAccessIterator first, RandomAccessIterator last); //dodanie void pop_heap (RandomAccessIterator first, RandomAccessIterator last); //zdjęcie void make_heap (RandomAccessIterator first, RandomAccessIterator last); void sort_heap (RandomAccessIterator first, RandomAccessIterator last); bool is_heap (RandomAccessIterator first, RandomAccessIterator last); 39 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<int>(cout, " ")); Wynik: Before pop: 6 5 3 4 2 1 After pop: 5 4 3 1 2 A[N-1] = 6 pop_heap(A, A+N); cout << endl << "After pop: "; copy(A, A+N-1, ostream_iterator<int>(cout, " ")); cout << endl << "A[N-1] = " << A[N-1] << endl; } 40 Obiekty funkcyjne (funktory) Obiekty, które mogą być wołane jak funkcje Zwykłe funkcje to też obiekty funkcyjne Operator () Modele: Generator, Unary Function, Binary Function Predicate, Binary Predicate Adaptacyjne obiekty funkcyjne 41 Przykłady: vector<int> V(100); generate(V.begin(), V.end(), rand); struct less_mag : public binary_function<double, double, bool> { bool operator()(double x, double y) { return fabs(x) < fabs(y); } }; vector<double> V; ... sort(V.begin(), V.end(), less_mag()); 42 Przykład: struct adder : public unary_function<double, void> { adder() : sum(0) {} double sum; void operator()(double x) { sum += x; } }; vector<double> V; ... adder result = for_each(V.begin(), V.end(), adder()); cout << "The sum is " << result.sum << endl; 43 Przykład: list<int> L; ... list<int>::iterator new_end = remove_if(L.begin(), L.end(), compose2(logical_and<bool>(), bind2nd(greater<int>(), 100), bind2nd(less<int>(), 1000))); L.erase(new_end, L.end()); 44 Dokumentacja i przykłady STL 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 45