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