Programowanie (język C) Kontenery Wykład 12. Tomasz Marks - Wydział MiNI PW -1- Tomasz Marks - Wydział MiNI PW -2- Rodzaje kontenerów Kontenery sekwencyjne kolekcje uporządkowane, w których kaŝdy element posiada określoną pozycję. Pozycja ta zaleŝy od momentu i miejsca wstawienia, nie zaleŝy od wartości elementu. Kontenery asocjacyjne kolekcje sortowane, w których chwilowa pozycja elementu zaleŝy od jego wartości zgodnie z przyjętym kryterium sortowania. UWAGA. Kontener asocjacyjny moŝe być rozpatrywany jako specjalny rodzaj kontenera sekwencyjnego, poniewaŝ kolekcje sortowane są porządkowane zgodnie z jakimś kryterium sortowania. Jednak kontenery asocjacyjne i sekwencyjne mają odrębne implementacje, które nie są wyprowadzane jedna z drugiej. Tomasz Marks - Wydział MiNI PW -3- Przegląd kontenerów kontenery sekwencyjne wektor <vector> lista <list> kolejka o dwóch końcach <deque> napis <string> kontenery asocjacyjne zbiór i multizbiór <set> mapa i multimapa <map> "prawie" kontenery adaptory kontenerów stos <stack> kolejka <queue> kolejka priorytetowa <queue> zbiór bitowy <bitset> "zwykła" tablica Tomasz Marks - Wydział MiNI PW -4-
Ilustracja kontenerów sekwencyjnych Ilustracja kontenerów asocjacyjnych Tomasz Marks - Wydział MiNI PW -5- Tomasz Marks - Wydział MiNI PW -6- Przykład: vector<>, deque<>, list<> [1] // kontenery.cpp #include<iostream> #include<vector> #include<deque> #include<list> int main ( ) int i; vector<int> V; Przykład: vector<>, deque<>, list<> [2]... deque<double> D; for ( i = 1; i <= 5; i ) D.push_front( i ); for ( i = 1; i <= 5; i ) D.push_back(i 10); for ( i = 0; i < D.size(); i ) cout << D[ i ] << ' '; // 5 4 3 2 1 11 12 13 14 15... for ( i = 1; i <= 10; i ) V.push_back( i ); for ( i = 0; i < V.size(); i ) cout << V[ i ] << ' '; // 1 2 3 4 5 6 7 8 9 10... Tomasz Marks - Wydział MiNI PW -7- Tomasz Marks - Wydział MiNI PW -8-
Przykład: vector<>, deque<>, list<> [3]... list<char> L; for ( char z = 'a'; z <= 'd'; z ) L.push_back( z ); L.push_front( 'y' ); while (! L.empty( ) ) cout << L.front( ) << ' '; L.pop_front( ); // nie zwraca elementu // y a b c d pair <> Tomasz Marks - Wydział MiNI PW -9- Tomasz Marks - Wydział MiNI PW -10- Wzorzec klasy pair<> Wzorzec struktury pair jest zdefiniowany w pliku nagłówkowym <utility>. template < class T1, class T2 > struct pair typedef T1 first_type; typedef T2 second_type; ; T1 first; T2 second; pair ( ) : first( T1( ) ), second( T2( ) ) pair ( const T1& a, const T2& b ) : first( a ), second( b ) template < class U, class V > pair ( const pair< U, V >& p ) : first( p.first ), second( p.second ) Wzorzec klasy pair<> Zdefiniowane są teŝ operatory porównania template < class T1, class T2 > bool operator== ( const pair<t1, T2>& x, const pair<t1, T2>& y ) return x.first == y.first && x.second == y.second; template < class T1, class T2 > bool operator< ( const pair<t1, T2>& x, const pair<t1, T2>& y ) return x.first < y.first (! (y.first < x.first ) && x.second < y.second ); Podobnie definiowane są operatory:!=, <=, >, >=. Tomasz Marks - Wydział MiNI PW -11- Tomasz Marks - Wydział MiNI PW -12-
Wzorzec klasy pair<> Dostępna jest teŝ uŝyteczna funkcja wzorcowa make_pair pozwalająca tworzyć pary wartości bez jawnego określania typów agumentów: template < class T1, class T2 > pair<t1, T2> make_pair ( const T1& x, const T2& y ) return pair < T1, T2 > ( x, y ); Zamiast pisać moŝemy napisać pair < int, const char * > ( 23, "osoba" ) make_pair ( 23, "osoba" ) Iteratory Tomasz Marks - Wydział MiNI PW -13- Tomasz Marks - Wydział MiNI PW -14- Iteratory (1) Uogólniają pojęcie wskaźnika, dzięki nim na kontenery moŝna patrzeć jak na ciągi, których elementy moŝna przeglądać kolejno. KaŜdy z kontenerów definiuje swój typ iteratora np. dla wektora, którego elementami są dane typu double typem iteratora jest vector<double>::iterator Iteratory nie tworzą hierarchii klas (nie ma bazowej klasy "ogólny iterator" ). Odpowiednie iteratory są zdefiniowane w plikach nagłówkowych dla kontenerów, pewne dodatkowe klasy i funkcje związane z iteratorami są zdefiniowane w pliku <iterator>. Iteratory często słuŝą do określania zakresów, np. algorytmy działają na zakresach określonych przez parę iteratorów. Iteratory (2) Zakres [first, last) oznacza, Ŝe element wskazywany przez first jest pierwszym elementem z zakresu, natomiast element wskazywany przez last jest bezpośrednio po ostatnim elemencie zakresu, czyli element wskazywany przez last nie naleŝy do zakresu. Nieprawidłowe korzystanie z iteratorów powoduje podobne problemy jak nieprawidłowe korzystanie ze wskaźników, w szczególności problemy się pojawią jeśli iterator: nie jest poprawnie zainicjowany, wyszedł poza ostatni element kontenera, wskazuje na usunięty element lub zlikwidowany kontener. Zwracaj uwagę na "uniewaŝnianie iteratorów", które moŝe nastąpić w wyniku wykonania operacji na kontenerze. Zasady uniewaŝniania iteratorów są róŝne dla róŝnych kontenerów, np. dla wektora operacja wstawienia elementu uniewaŝnia wszystkie iteratory związane z tym wektorem Nie ma czegoś takiego jak "zerowy" iterator. Tomasz Marks - Wydział MiNI PW -15- Tomasz Marks - Wydział MiNI PW -16-
wejściowy wyjściowy postępujący dwukierunkowy swobodny Kategorie iteratorów odczyt v=*p p->x zapis *p=v inkrementacja p p dekrementacja --p p-- porównania p1==p2 p1!=p2 przypisanie p1=p2 dodatkowe // kontenery2.cpp #include<iostream> #include<set> int main ( ) typedef set< int > ISet; ISet zbior; ISet::const_iterator pos; Przykład: [multi] set<> zbior.insert( 3 ); zbior.insert( 1 ); zbior.insert( 5 ); zbior.insert( 4 ); zbior.insert( 2 ); zbior.insert( 6 ); zbior.insert( 2 ); for ( pos = zbior.begin( ); pos!= zbior.end( ); pos ) cout << *pos << ' '; // 1 2 3 4 5 6 Tomasz Marks - Wydział MiNI PW -17- Tomasz Marks - Wydział MiNI PW -18- // kontenery3.cpp #include<iostream> #include<string> #include<map> int main ( ) typedef multimap< int, string > ISMM::iterator it; ISMM mmapa; Przykład: [multi] map<> ISMM; mmapa.insert( make_pair( 3, "KOTA" ) ); mmapa.insert( make_pair( 4, "A" ) ); mmapa.insert( make_pair( 7, "ALE" ) ); mmapa.insert( make_pair( 3, "KOTA" ) ); mmapa.insert( make_pair( 2, "MA" ) ); mmapa.insert( make_pair( 6, "MA" ) ); mmapa.insert( make_pair( 5, "KOT" ) ); mmapa.insert( make_pair( 1, "ALA" ) ); for ( it = mmapa.begin( ); it!= mmapa.end( ); it ) cout << it->second << ' '; // ALA MA KOTA KOTA A KOT MA ALE Tomasz Marks - Wydział MiNI PW -19- Iteratory wejściowe i wyjściowe iteratory wejściowe słuŝą do jednorazowego odczytywania informacji kaŝdy element moŝna odczytać dokładnie raz, tzn. jeśli utworzymy kopię iteratora, a potem oba będziemy przesuwać otrzymamy inne wyniki iteratorami wejściowmi są iteratory związane ze strumieniami wejściowymi iteratory wyjściowe słuŝą do zapisywania informacji dwukrotny zapis wartości bez przesuwania iteratora nie powoduje nadpisania poprzedniej informacji, a zapis w nowym miejscu nie ma Ŝadnej kontroli poprawności wykonanego zapisu iteratorami wyjściowymi są iteratory związane ze strumieniami wyjściowymi oraz insertery (wstawiacze) dla iteratorów wejściowych i wyjściowych praktycznie nie moŝna rozdzielić operacji odczytu/zapisu od inkrementacji Tomasz Marks - Wydział MiNI PW -20-
Iteratory postępujące i dwukierunkowe Iteratory o dostępie swobodnym iteratory postępujące są kombinacją iteratorów wejściowych i wyjściowych, za ich pomocą moŝna zarówno odczytywać jak i zapisywać elementy kolekcji kaŝdy z elementów moŝna przetwarzać wielokrotnie w przypadku zapisu następuje nadpisanie poprzedniej wartości, a nie dodanie nowej jak dla iteratorów wyjściowych nie są kategorią związaną z Ŝadną standardową kolekcją iteratory dwukierunkowe mają wszystkie cechy iteratorów postępujących i dodatkową moŝliwość cofania się do poprzedniego elementu iteratorami dwukierunkowymi są iteratory związane z listami i wszystkimi kontenerami asocjacyjnymi są dokładnym odpowiednikiem wskaźników dodatkowe operacje (oprócz wszystkich dla dwukierunkowych) indeksowanie - swobodny dostęp do n-tego elementu kolekcji iter[n] przesunięcie o n elementów itern, niter, iter-n, iter=n, iter-=n odejmowanie iteratorów (wskazujących elementy tej samej kolekcji) iter1 - iter2 relacje pomiędzy iteratorami (wskazujących elementy tej samej kolekcji) iter1<iter2, iter1>iter2, iter1<=iter2, iter1>=iter2 iteratorami o dostępie swobodnym są iteratory związane z wektorami, kolejkami o dwóch końcach, napisami (string) wskaźniki do elementów tablic moŝna traktować jako iteratory o dostępie swobodnym Tomasz Marks - Wydział MiNI PW -21- Tomasz Marks - Wydział MiNI PW -22- Iteratory - uzupełnienie wszystkie kategorie iteratorów udostępniają konstruktor kopiujący wszystkie kategorie iteratorów oprócz wyjściowych udostępniają konstruktor bezparametrowy wszystkie operacje na iteratorach wykonują się w stałym czasie (tzn. niezaleŝnym od rozmiaru kolekcji; dlatego np. indeksowanie dostępne jest jedynie dla iteratorów swobodnych, dla innych nie da się go zrealizować w czasie stałym) ale to nie znaczy, Ŝe wszystkie w takim samym czasie np. p jest szybsze (i dla tego zalecane) niŝ p kaŝdy z kontenerów oprócz odpowiedniego typu iterator udostępnia równieŝ typ const_iterator - za jego pośrednictwem nie moŝna zmienić wartości elementów (lepiej korzystać ze zwykłych iteratorów) np. vector<int>::iterator it1 // zwykły iterator vector<int>::const_iterator it2 // stałe elementy const vector<int>::iterator it3 // stały iterator Iteratory wstawiające - wstęp "zwykłe" iteratory związane z kolekcjami nie nadają się do wstawiania (dodawania) nowych elementów do kolekcji #include <list> int main( ) int tab[ ] = 1, 2, 3 ; list < int > L; list < int > :: iterator it = L.begin( ); for ( int i = 0; i < 3; i, it ) *it = tab[ i ]; // błąd! - próba nadpisania // na nieistniejącym elemencie Tomasz Marks - Wydział MiNI PW -23- Tomasz Marks - Wydział MiNI PW -24-
Iteratory wstawiające - przykład do dodawania nowych elementów do kolekcji słuŝą iteratory wstawiające #include <list> int main( ) int tab[ ] = 1, 2, 3 ; list < int > L; back_insert_iterator < list < int > > it( L ); W przykładzie pojawił się zapis UWAGA!!! back_insert_iterator < list < int > > it( L ); pomiędzy ostatnimi dwoma znakami > musi być uŝyty odstęp (spacja, tabulacja, nowa linia, komentarz). Wszystkie pozostałe odstępy moŝna pominąć. Zapis back_insert_iterator<list<int> >it(l); back_insert_iterator<list<int>>it(l); for ( int i = 0; i < 3; i ) // it pominięte *it = tab[ i ]; // O.K. moŝna napisać it = tab[ i ] jest niepoprawny. Wynika to z faktu, Ŝe >> jest leksemem języka C (jednostka leksykalna oznaczająca operator). Tomasz Marks - Wydział MiNI PW -25- Tomasz Marks - Wydział MiNI PW -26- Iteratory wstawiające - szczegóły (1) operacje it oraz it są dla iteratorów wstawiających operacjami pustymi i zaleca się ich nie pisać operacja *it = v (i równowaŝny zapis it = v) wstawia element o wartości v do kolekcji wywołując odpowiednią metodę kontenera, dlatego w deklaracji iteratora wstawiającego musi być podany typ kontenera (parametr szablonu), a w konstruktorze wskazany konkretny kontener powyŝszy sposób implementacji iteratorów wstawiających spełnia wymagania iteratorów wyjściowych deklaracje back_insert_iterator < Tc < Te > > iter( cont ) // wstawiacz końcowy front_insert_iterator < Tc < Te > > iter( cont ) // wstawiacz początkowy insert_iterator < Tc < Te > > iter( cont, pos ) // wstawiacz ogólny gdzie Tc<Te> - kontener Tc (np. vector) o elementach Te (np. int), iter - deklarowany wstawiacz, cont - konkretny kontener, pos - pozycja (zwykły iterator). Iteratory wstawiające - szczegóły (2) wstawiacze końcowe (back inserters) wykorzystują metodę kontenera push_back( v ) są dostępne dla wszystkich kontenerów sekwencyjnych (bo mają one metodę push_back ) wstawiacze początkowe (front inserters) wykorzystują metodę kontenera push_front( v ) są dostępne dla list i kolejek o dwóch końcach (bo tylko one mają metodę push_front ) wstawiacze ogólne (inserters) wykorzystują metodę kontenera insert( v, it ) są dostępne dla wszystkich kontenerów standardowych, ale dla kontenerów sekwencyjnych wstawiają element na wskazaną przez it pozycję dla kontenerów asocjacyjnych wstawiają zgodnie z uporządkowaniem Tomasz Marks - Wydział MiNI PW -27- Tomasz Marks - Wydział MiNI PW -28-
Iteratory wsteczne kaŝdy z kontenerów standardowych dostarcza typ iteratora umoŝliwiający przeglądanie elementów kolekcji w kolejności odwrotnej begin( ) end( ) Iteratory wsteczne - przykład int main( ) vector < int > v; back_insert_iterator < vector < int > > ins( v ); for ( int i = 1; i <= 5; i ) ins = i; rend( ) rbegin( ) deklaracja iteratora wstecznego (odwrotnego) rit jest następująca Tc<Te>::reverse_iterator rit; Tc<Te>::reverse_iterator rit( it ); // inicjowany iteratorem it gdzie Tc<Te> - kontener Tc (np. vector) o elementach Te (np. int) np. vector < int > :: reverse_ iterator r_iter; vector < int > :: iterator it1( v.begin( ) 2 ); cout << *it1 << endl; // wynik: 3 vector < int > :: reverse_iterator rit( it1 ); cout << *rit << endl; // wynik: 2 vector < int > :: iterator it2; it2 = rit.base( ); // base( ) zwraca iterator bazowy dla odwrotnego cout << *it2 << endl; // wynik: 3 Tomasz Marks - Wydział MiNI PW -29- Tomasz Marks - Wydział MiNI PW -30- Iteratory strumieniowe iteratory strumieniowe wyjściowe są w pełni analogiczne do iteratorów wstawiających, z tym Ŝe "kolekcją", do której wstawiamy jest strumień wyjściowy deklaracje ostream_iterator < T > iter( os ); // iterator związany z os ostream_iterator < T > iter( os, delim ); // iterator związany z os gdzie T - typ elementu; delim - separator elementów (char*) iteratory strumieniowe wejściowe "kolekcją", z której czytamy jest strumień wejściowy deklaracja (T - typ elementu) istream_iterator < T > iter( is ); // iterator związany z is istream_iterator < T > iter; // iterator "końca strumnienia" gdzie T - typ elementu; mogą być zaimplementowane w róŝny sposób, n.p.: wczytanie danych odbywa się w chwili wykonania *iter wczytanie danych odbywa się w chwili wykonania iter ( iter ) oraz deklaracji (sic!), *iter jest operacją pustą Iteratory strumieniowe - przykład int main( ) vector <int> v; back_insert_iterator <vector < int > > ins( v ); istream_iterator < int > we( cin ); istream_iterator < int > we_end; do ins = *we; we; while ( we!= we_end ); ostream_iterator < int > wy( cout, ", " ); copy( v.begin( ), v.end( ), wy ); Tomasz Marks - Wydział MiNI PW -31- Tomasz Marks - Wydział MiNI PW -32-
Koniec wykładu 12. Tomasz Marks - Wydział MiNI PW -33-