Wstęp do programowania obiektowego
STL - Standard Template Library
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.
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
Jak to działa?
Dlaczego STL?
Elastyczność
Efektywność
Dobra specyfikacja i dokumentacja
STL Containers
(zbiorniki \ kontenery)
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)
#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;
}
Zbiornik vector
#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
#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
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>
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();
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();
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.
Metody priority_queue:
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 (napis)
Efektywna implementacja
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ć
struct ltstr{
bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0;
} };
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;
Przykład użycia map
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));
}
Alokatory
Zawierają funkcje alokacji i dealokacji pamięci
Czarne skrzynki
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.
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
Rodzaje iteratorów
Input Iterator
Output Iterator
Forward Iterator
Bidirectional Iterator
Random Access Iterator
Const Iterator
Input Iterator
Output Iterator
Forward Iterator
Bidirectional Iterator
Random Access Iterator
Algorytmy STL
Generyczne algorytmy oddzielają algorytm od danych
„Nie zależą” od reprezentacji danych
Operują na iteratorach
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>
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>
iterator_traits<InputIterator>::difference_type count
(InputIterator first, InputIterator last, const
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);
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);
Przykład (kopiowanie zawartości vectora do listy i sprawdzenie zgodności):
vector<int> V(5); // utworzenie vectora V
iota(V.begin(), V.end(), 1); //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()));
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 (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"
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);
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' };
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, " "));
Wynik:
Intersection of A1 and A2: 1 3 5 Intersection of A3 and A4: a b b f h
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);
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, " "));
pop_heap(A, A+N);
cout << endl << "After pop: ";
copy(A, A+N-1, ostream_iterator<int>(cout, " "));
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łane jak funkcje
Zwykłe funkcje to też obiekty funkcyjne
Operator ()
Modele: Generator, Unary Function, Binary Function
Predicate, Binary Predicate
Adaptacyjne obiekty funkcyjne
1 przykład – sortowanie według modułu liczby:
vector<int> V(100);
generate(V.begin(), V.end(), rand);
struct mniejszy_modul : 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(), mniejszy_modul());
2 przykład - sumator:
struct dodawacz: 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(), dodawacz());
cout << "The sum is " << result.sum << endl;
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