Szablony w C++
Transkrypt
Szablony w C++
Wzorce (szablony) Wzorce pozwalaj na okre lenie, za pomoc pojedynczego fragmentu kodu, całej gamy powi zanych (przeci onych) funkcji – nazywanych funkcjami wzorcowymi – lub powi zanych kas – nazywanych klasami wzorcowymi. Mo emy napisa pojedynczy wzorzec funkcji dla funkcji sortowanie tablicy, a C++ wygeneruje automatycznie oddzielne funkcje wzorcowe porz dkuj ce tablic int, tablic float, tablic napisów i tak dalej. Mo emy napisa pojedynczy wzorzec klasy dla klasy stosu, co spowoduje, e C++ automatycznie wygeneruje oddzielne klasy wzorcowe, na przykład klas stosu dla int, klas stosu dla float, czy klas stosu dla napisów string. Nale y zwróci uwag na ró nic pomi dzy wzorcem funkcji/klasy, a funkcj /klas wzorcow . Wzorce funkcji i klas s tylko szablonem w oparciu o który kre limy kształt. Funkcje i klasy wzorcowe s niczym tak jakby przekalkowane, lecz oddzielne rysunki zawieraj ce ten sam kształt, które mog , na przykład, zosta pomalowane na ró ne kolory. Wszystkie definicje wzorców zaczynaj si od słowa kluczowego template poprzedzaj cego zawart w nawiasach k towych (<i>) list parametrów formalnych wzorca funkcji. Ka dy z tych parametrów, reprezentuj cy typ, musi by poprzedzony słowem kluczowym class, tak jak przedstawiono poni ej: template<class T> {definicja wzorca} lub template<class TypElementu> {definicja wzorca} lub template<class TypElementu1, class TypElementu2> {definicja wzorca} Parametry formalne w definicji wzorca wykorzystywane s (podobnie jak argumenty typów wbudowanych i zdefiniowanych przez u ytkownika) do okre lenia typów wykorzystanych w definicji wzorca. Zwró my uwag , e słowo kluczowe class wykorzystywane do okre lenia typów parametrów wzorca oznacza tu "ka dy typ wbudowany lub typ zdefiniowany przez u ytkownika". Wzorce funkcji Wzorce funkcji stosowane s do wykonywania identycznych operacji na ró nych typach danych. Programista pisze pojedyncz definicje wzorzec danej funkcji. Kompilator opieraj c si na typach argumentów dostarczonych w jej wywołaniu, automatycznie generuje oddzielny kod wynikowy funkcji do odpowiedniej obsługi ka dego wywołania. http://kgb007.republika.pl/ Zbadajmy wzorzec funkcji drukTabl 1. 2. 3. 4. 5. 6. 7. 8. template<class T> void drukTabl(const T *tablica, const int licznik) { for (int i=0; i< licznik ; i++) cout<<tablica[i]<<" "; cout<<endl; } Wzorzec funkcji drukTab deklaruje pojedynczy parametr formalny T (T mo e by dowolnym wa nym identyfikatorem) dla typu tablicy drukowanej przez funkcj drukTabl. T jest tu parametrem typu. Gdy kompilator wykryje w kodzie ródłowym programu wywołanie tej funkcji, typ jej pierwszego argumentu staje si substytutem T w definicji wzorca, a C++ tworzy kompletn funkcje wzorcow dla druku tablicy zawieraj cej okre lony typ danych. Nast pnie nowo utworzona funkcja zostaje skompilowana. Na przykład, realizacja dla typu int wygl da nast puj co: void drukTabl(const int *tablica, const int licznik) { for(int i=0; i<licznik; i++) cout<<tablica[i]<<" "; cout<<endl; } Ka dy fragment formalny w definicji wzorca funkcji powinien wyst pi przynajmniej raz na li cie parametrów funkcji. Natomiast jego nazwa mo e wyst pi tylko raz na li cie parametrów w nagłówku wzorca. Nie musi by ona unikatowa dla jednej funkcji wzorcowej. Przykład: #include <iostream.h> template< class T > void drukTabl(const int *tablica, const int licznik) { for ( int i = 0; i < licznik; i++ ) cout << tablica[ i ] << " "; } cout << endl; http://kgb007.republika.pl/ int main() { int a[5] = { 1, 2, 3, 4, 5 }; double b[ 5] = { 1.1, 2.2, 3.3, 4.4, 5.5 }; char c[5] = "asdfg"; drukTabl ( a, 5 ); // funkcja wzorcowa dla int drukTabl ( b, 5 ); // funkcja wzorcowa dla double drukTabl ( c, 5 ); // funkcja wzorcowa dla char return 0; } W tym przykładzie, mechanizm wzorców uwalnia programist od konieczno ci pisania trzech oddzielnych funkcji przeci onych. Wzorce klas Wzorce klas s nazywane typami parametryzowanymi (ang. parametrized types), poniewa wymagaj jednego lub wi cej parametrów do okre lenia, jak dostosowa wzorzec, „klasy ogólnej” do formy okre lonych klas wzorcowych. Programista, który pragnie stworzy szereg klas wzorcowych, mo e po prostu napisa jedn definicje wzorca klasy. Za ka dym razem, gdy potrzebuje nowej realizacji dla okre lonego typu danych, stosuje prost , zwi zł notacje i kompilator tworzy kod ródłowy dla potrzebnej klasy wzorcowej. Na przykład, jeden wzorzec klasy Stos mo e sta si podstaw do utworzenia szeregu, wykorzystywanych w programie, klas o ró nych warto ciach (jak „Stos double”, „Stos int”, „Stos char”, „Stos pracownik” itd.). Zwró my uwag na definicje wzorca klasy Stos na poni szym rysunku. Wygl da ona jak zwykła definicja klasy, jest tylko poprzedzona nagłówkiem template<class T> w celu okre lenia, e jest to definicja wzorca klasy z parametrem typu T wskazuj cym rodzaj tworzonej klasy Stos. Programista nie musi wykorzystywa jako identyfikatora T – mo e by tu zastosowany dowolny. Typ elementu przechowywanego w klasie Stos jest okre lony jako T w nagłówku klasy i definicjach funkcji składowych. Za chwil zobaczymy, jak T zostaje powi zane z okre lonym typem, jak double lub int. http://kgb007.republika.pl/ #ifndef TStos1_H #define TStos1_H // #include <iostream.h> // template< class T > class Stos { public: Stos( int = 10 ); // domy lny konstruktor (Stos size 10) ~Stos() { delete [] StosPtr; } // destructor bool push( const T& ); // umie element na stosie bool pop( T& ); // pobierz element ze stosu private: int size; // ilo elementów na stosie int top; // poło enie elementu na szczycie stosu T *StosPtr; //wska nik na stos // bool isEmpty() const { return top == -1; } //funkcje pomocnicze bool isFull() const { return top == size - 1; } // }; // // Constructor with default size 10 template< class T > Stos< T >::Stos( int s ) { size = s > 0 ? s : 10; top = -1; //Pocz tkowo stos jest pusty StosPtr = new T[ size ]; // alokacja miejsca dla elementów } // Umie elementy na stosie // zwró 1 je łi operacja przebiegła pomy lnie, 0 w przeciwnym przypadku template< class T > bool Stos< T >::push( const T &pushValue ) { if ( !isFull() ) { StosPtr[ ++top ] = pushValue; // umie na stosie return true; //udane umieszczenie } return false; //nieudane umieszczenie } //Pobierz element ze stosu template< class T > bool Stos< T >::pop( T &popValue ) http://kgb007.republika.pl/ { if ( !isEmpty() ) { popValue = StosPtr[ top-- ]; //usu element ze stosu return true; // udane pobranie } return false; //nieudane pobranie } #endif Rozwa my teraz program obsługi (funkcj main) testuj cy wzorzec klasy Stos. Rozpoczyna si on od realizacji doubleStos o wielko ci 5 elementów. Obiekt ten jest zadeklarowany jako klasa Stos<double> (wymawiaj „Stos typu double”). Kompilator kojarzy typ double z parametrem typu T we wzorcu w celu utworzenia kodu ródłowego klasy Stos dla typu double. Chocia program nie widzi tego kodu ródłowego, jest doł czany do niego i kompilowany. #include <iostream.h> #include "tStos1.h" int main() { Stos< double > doubleStos( 5 ); double f = 1.1; cout << "Umieszczenie elementów na stosie doubleStos\n"; while ( doubleStos.push( f ) ) { // udana operacja zwraca true cout << f << ' '; f += 1.1; } cout << "\nStos jest pełny. Nie mo na umie ci " << f << "\n\nPobieranie elementów ze stosu doubleStos\n"; while ( doubleStos.pop( f ) ) // udana operacja zwraca true cout << f << ' '; cout << "\nStos jest pusty. Nie mo na nic pobra \n"; Stos< int > intStos; int i = 1; cout << "\nUmieszczanie elementów na stosie intStos\n"; while ( intStos.push( i ) ) { // udana operacja zwraca true cout << i << ' '; ++i; } cout << "\nStos jest pełny. Nie mo na umie ci " << i http://kgb007.republika.pl/ << "\n\nPobieranie elementów ze stosu intStos\n"; while ( intStos.pop( i ) ) //udana operacja zwraca true cout << i << ' '; } cout << "\nStos jest pusty nie mo na nic pobra \n"; return 0; Program obsługi kolejno umieszcza warto ci double 1.1, 2.2, 3.3, 4.4 i 5.5 na stosie doubleStos. P tla push ko czy si , gdy program ten usiłuje umie ci szóst warto (stos jest pełny, poniewa został utworzony do przechowywania maksymalnie 5 elementów). Nast pnie program pobiera ze stosu pi warto ci. Program próbuje pobra szóst warto , ale doubleStos jest ju pusty, wi c p tla pop ko czy si . Nast pnie, za pomoc deklaracji Stos<int> intStos program realizuje stos intStos dla liczb całkowitych (wymawiaj „intStos to stos typu int”). Poniewa wielko stosu nie jest okre lona, przyj ta zostaje domy lna wielko 10, zdefiniowana w domy lnym konstruktorze. I znów, program w p tli umieszcza warto ci na stosie intStos, do momentu zapełnienia go, a nast pnie pobiera je, a opró ni stos. Podobnie jak poprzednio, warto ci pobierane s w kolejno ci „ostatnia wpisana, pierwsza pobrana”. Ka da definicja funkcji składowej, znajduj ca si poza nagłówkiem wzorca klasy, rozpoczyna si od nagłówka template<class T> Ka da definicja funkcji jest podobna do zwykłej definicji, z wyj tkiem tego, e typ elementu Stos wymieniany jest zawsze ogólnie jako parametr typu T. Operator zasi gu jest wykorzystywany w nazwie wzorca klasy Stos<T> do powi zania definicji funkcji z zasi giem wzorca klasy. W tym przypadku, nazwa klasy to Stos<T>. Gdy realizowana jest klasa doubleStos typu Stos<double>, konstruktor Stos wyko ystuje new do utworzenia tablicy elementów typu double reprezentuj cych stos. Instrukcja stosPtr = new T[size]; w definicji wzorca klasy Stos jest definiowana przez kompilator w klasie wzorcowej Stos<double> jako stosPtr = new double[size]; Zwró my uwag , e kod funkcji main jest podobny zarówno przy wyko ystaniu z doubleStos w pocz tkowej cz ci main, jak i przy u yciu intStos w jego dalszej cz ci. Standardowa biblioteka wzorców (STL – Standard Template Library) Pliki nagłówkowe zasobników Biblioteki Standardowej <vector> <list> <deque> <stack> http://kgb007.republika.pl/ <map> <set> <bitset> Zasobnik sekwencyjny Vector Klasa vector zawiera struktur danych z s siaduj cym umiejscowieniem w pami ci. Umo liwia w ten sposób wydajny, bezpo redni dost p do dowolnego elementu wektora przez operator indeksu [], dokładnie jak w „surowej” tablicy C lub C++. Klasa vector jest najcz ciej u ywana, kiedy dane w zasobniku musz by sortowane i łatwo dost pne przez indeks. Kiedy pami vector zostaje wyczerpana, vector automatycznie przydziela wi kszy ci gły obszar pami ci, kopiuje oryginalne elementy do nowej pami ci i zwalnia star . Zasobnik sekwencyjny list Zasobnik sekwencyjny list umo liwia wydajn implementacje operacji wstawiania i usuwania z dowolnej jego pozycji. Je li wi kszo wstawie i usuni zachodzi na ko cach zasobnika, struktura danych deque przekazuje bardziej efektywn implementacj . Klasa list jest implementowana jako lista z dwukierunkowymi odno nikami – tj. ka dy w zeł listy zawiera wska nik do poprzedniego i nast pnego. To umo liwia klasie list obsług dwukierunkowych iteratorów, które pozwalaj na przechodzenie przez zasobnik zarówno do przodu, jak i w odwróconym porz dku. Dowolny algorytm, który wymaga iteratorów wej ciowych, wyj ciowych, do przodu lub dwukierunkowych mo e operowa na obiekcie typu list. Wiele z funkcji składuwych list manipuluje elementami zasobnika, jak uporz dkowanym zbiorem elementów. Zasobnik sekwencyjny deque Klasa deque dostarcza bazdzo po ytecznych klas vector i list w jednym zasobniku. Termin deque jest skrótem „kolejki dwukierunkowej”. Klasa deque jest równie zaimplementowana w celu dostarczenia wydajnego indeksowanego dost pu (z u yciem indeksu) do czytania i modyfikowania swoich elementów bardzo podobnie do klasy vector. Klasa deque jest równie implementowana do efektywnych operacji wstawiania na jej pocz tku i ko cu bardzo podobnie do klasy list (chocia list ma równie mo liwo wydajnego usuwania i wstawiania w rodku obiektu typu list). Klasa deque stosuje obsług iteratorów o dost pie bezpo rednim, wi c jej obiekty mog by u ywane ze wszystkimi algorytmami STL. Jednym z najpowszechniejszych zastosowa deque jest utrzymanie kolejki elementów „pierwszy-wyszedł-pierwszy-wszedł” (FIFO). Dodatkowa pami dla deque mo e by przydzielana na obu ko cach obiektu deque w blokach pami ci, które s zazwyczaj trzymane jako tablice wska ników do tych bloków. Odpowiednio do nie s siaduj cego rozmieszczenia pami ci klasy deque, iterator tej klasy musi by bardziej inteligentny od wska ników u ywanych do iteracji przez klas vector lub dla opartych na wska nikach tablic. Zasobniki skojarzeniowe (asocjacyjne) http://kgb007.republika.pl/ Zasobniki skojarzeniowe STL umo liwiaj bezpo redni dost p do przechowywania i odzyskiwania elementów przez klucze (cz sto nazywane kluczami wyszukiwawczymi). Cztery zasobniki skojarzeniowe to multiset, set, multimap i map. W ka dym z nich, klucze s utrzymane w posortowanym porz dku. Iterowanie przez zasobnik asocjacyjny bada go w jego własnym porz dku sortowania. Klasy multiset i set umo liwiaj operacje do manipulowania zestawami danych, gdzie te warto ci s kluczami – nie ma oddzielnej warto ci skojarzeniowej z ka dym kluczem. Główn ró nic mi dzy multiset a set jest to, e multiset dopuszcza do duplikowania kluczy, a set nie. Zasobnik skojarzeniowy multiset Zasobnik skojarzeniowy multiset słu y do szybkiego zapami tywania i odzyskiwania kluczy oraz dopuszcza ich duplikowanie. Uporz dkowanie elementów jest okre lone przez obiekt funkcji porównania. Dla przykładu, w obiekcie multiset typu całkowitego elementy mog by sortowane w porz dku rosn cym przez porz dkowanie kluczy za pomoc obiektu funkcji less<int>. Typ danych kluczy we wszystkich zasobnikach asocjacyjnych musi poprawnie obsługiwa porównanie w oparciu o okre lony obiekt funkcji porównania – klucze sortowane za pomoc less<int> musz obsługiwa porównanie za pomoc iteratora <. Je eli klucze u yte w zasobnikach asocjacyjnych s typu danych zdefiniowanych przez programist , typy te musz dostarcza odpowiednie operatory porównania. Bibliografia [1] Harvey M. Deitel, Paul J. Deitel - Arkana C++ Programowanie. http://kgb007.republika.pl/