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/

Podobne dokumenty