Czym są? Szablony (wzorce) są czymś podobnym do makr, tyle że wykonywane są przez kompilator, a nie przez preprocesor. Cechują je pewne własności, których jednak nie ma preprocesor, np. można tworzyć rekurencyjne wywołania. Ale zobaczmy najpierw taką sytuację: chcemy utworzyć klasę o nazwie Punkt o trzech współrzędnych: x, y, z. Potrzebujemy trzy różne implementacje. Jedna ma działać na liczbach typu unsigned int, druga na liczbach typu int, a trzecia na float. Pierwsze, o czym pomyślimy, to napisać coś takiego (jeśli dostrzeżesz błąd, to się nie przejmuj). Mamy przykładową klasę:
// ... class PunktUInt { public: PunktUInt( unsigned argX, unsigned argY, unsigned argZ ) : x(argX), y(argY), z(argX)
unsigned x, y, z;
// ...
I potem co? kopiujemy i zmieniamy unsigned na int:
// ... class PunktInt { public: PunktInt( int argX, int argY, int argZ ) : x(argX), y(argY), z(argZ)
int x, y, z;
// ... Następnie zamieniamy na float:
// ... class PunktFloat { public: PunktFloat( float argX, float argY, float argZ ) : x(argX), y(argY), z(argX)
float x, y, z;
// ... Uff! Wreszcie napisaliśmy - pomyślisz sobie. Jednak pisanie wymaga trochę wysiłku. No dobrze, teraz tworzymy funkcję main:
int main(void) { PunktInt A(0,-10,0); PunktUInt B(0,10,5);
std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl; std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
I oto po naszych ciężkich staraniach otrzymujemy dość ciekawy i niespodziewany wynik:
A(0,-10,0) B(0,10,0)
Ojej! - zapewne krzykniesz. Musiał gdzieś się tu zjawić błąd. Trzeba znaleźć go. Aha, mamy go:
// ... PunktUInt( unsigned argX, unsigned argY, unsigned argZ ) : x(argX), y(argY), '''z(argX)''' // ...
Zamiast z(argX) powinno być z(argZ). I trzeba teraz wszystko poprawiać, w tym także resztę funkcji... Dobrze, że to tylko tyle. Ale na szczęście C++ daje nam prostszy sposób.
Wyróżniamy różne możliwości szablonów:
Wykorzystywanie szablonów
Napiszmy teraz jeszcze raz nasz program. Tym razem z wykorzystaniem szablonów.
// ... template class Punkt { public: Punkt( T argX, T argY, T argZ ) : x(argX), y(argY), z(argX)
T x, y, z;
// ... Za pomocą template tworzymy nasz szablon. Parametrem jest typ, jaki chcemy użyć, tworzymy go poprzez . Teraz, aby utworzyć nasze punkty możemy zapisać:
Punkt A(0,-10,0); Punkt A(0,10,5);
Czyli nasz main będzie wyglądał tak:
int main(void) { Punkt A(0,-10,0); Punkt B(0,10,5);
std::cout << "A(" << A.x << "," << A.y << "," << A.z PunktInt; typedef Punkt PunktUInt; typedef Punkt PunktFloat; I tyle. Main zamieni nam się wtedy w pierwotną formę:
int main(void) { PunktInt A(0,-10,0); PunktUInt B(0,10,5);
std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl; std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
Powiesz:
I masz rację, bo zobaczysz:
A(0,-10,0) B(0,10,0)
Powód jest ten sam co poprzedni. Punkt( T argX, T argY, T argZ ) : x(argX), y(argY), '''z(argX)'''
Musimy zamienić z(argX) na z(argZ) i będzie wszystko w porządku. Tylko tyle. Nie wierzysz? Ale to jest prawda. To zobacz cały nasz program powinien wyglądać w ten sposób: #include
template class Punkt { public: Punkt( T argX, T argY, T argZ ) : x(argX), y(argY), z(argZ)
T x, y, z;
typedef Punkt PunktInt; typedef Punkt PunktUInt; typedef Punkt PunktFloat;
int main(void) { PunktInt A(0,-10,0); PunktUInt B(0,10,5);
std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl; std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
Szablony z wieloma parametrami
Szablon może także mieć więcej niż jeden parametr. Na przykład chcielibyśmy posługiwać się parami obiektów. Należy więc napisać klasę Para, zawierającą dwa elementy: pierwszy o nazwie pierwszy, a drugi o nazwie drugi, jednakże nie wiemy z góry, jakie mają one mieć typy. Możemy to zrobić w ten sposób:
#include #include template class Para { public: Para()
Para( T1 a, T2 b ) : pierwszy(a), drugi(b)
T1 pierwszy; T2 drugi;
int main(void) { // tworzymy nasz obiekt Para zmienna("Liczba",10);
std::cout utworzyliśmy szablon o dwóch parametrach.
Deklaracja zmiennej zmienna określa jej typ poprzez skonkretyzowanie typów w szablonie, pośrednio więc określa też, jakie typy będą miały składowe tej zmiennej.
Można też liczby
Parametrem szablonu może być także liczba. Zilustrujmy to przykładem: #include #include template class Tablica { public:
T &operator[]( std::size_t i )
return tabl[i];
private: T tabl[N];
int main(void) { Tablica A;
for ( int i=0; i<10; ++i )
A[i]=100+i;
for ( int i=0; i<10; ++i )
std::cout << "A[" << i << "]=" << A[i] << std::endl;
return 0; } W powyższym przykładzie użyto typu std::size_t, zadeklarowanego w pliku nagłówkowym dołączanym dyrektywą .