C++/Szablony klas
Z Wikibooks, biblioteki wolnych podręczników.
Spis treści |
[edytuj] 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):
// ... class PunktUInt { public: PunktUInt( unsigned _x, unsigned _y, unsigned _z ) : x(_x), y(_y), z(_x) { } unsigned x, y, z; }; // ...
I potem co? kopiujemy i zmieniamy unsigned na int:
// ... class PunktInt { public: PunktInt( int _x, int _y, int _z ) : x(_x), y(_y), z(_x) { } int x, y, z; }; // ...
Następnie zamieniamy na float:
// ... class PunktFloat { public: PunktFloat( float _x, float _y, float _z ) : x(_x), y(_y), z(_x) { } 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 _x, unsigned _y, unsigned _z ) : x(_x), y(_y), '''z(_x)''' // ...
Zamiast z(_x) powinno być z(_z). 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.
[edytuj] Wykorzystywanie szablonów
Napiszmy teraz jeszcze raz nasz program. Tym razem z wykorzystaniem szablonów:
// ... template <class Typ> class Punkt { public: Punkt( Typ _x, Typ _y, Typ _z ) : x(_x), y(_y), z(_x) { } Typ x, y, z; }; // ...
Za pomocą template<class Typ> tworzymy nasz szablon. Parametrem jest typ, jaki chcemy użyć, tworzymy go poprzez <class Typ>. Teraz, aby utworzyć nasze punkty możemy zapisać:
Punkt<int> A(0,-10,0); Punkt<unsigned> A(0,10,5);
Czyli nasz main będzie wyglądał tak:
int main(void) { Punkt<int> A(0,-10,0); Punkt<unsigned> 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; }
Ale nie podoba nam się ta notacja, bo na przykład program za bardzo zaczyna nam przypominać HTML. Co mamy zrobić? Nic innego, jak tylko przed funkcją main skorzystać z typedef:
typedef Punkt<int> PunktInt; typedef Punkt<unsigned> PunktUInt; typedef Punkt<float> 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; }
- "No dobra, ale jak teraz uruchomię ten program to nadal mam ten sam, zły wynik" - powiesz. I masz rację, bo zobaczysz:
A(0,-10,0) B(0,10,0)
Powód jest ten sam co poprzedni.
Punkt( Typ _x, Typ _y, Typ _z )
: x(_x), y(_y), z(_x)
{ }
Musimy zamienić z(_x) na z(_z) 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 <iostream> template <class Typ> class Punkt { public: Punkt( Typ _x, Typ _y, Typ _z ) : x(_x), y(_y), z(_z) { } Typ x, y, z; }; typedef Punkt<int> PunktInt; typedef Punkt<unsigned> PunktUInt; typedef Punkt<float> 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; }
[edytuj] Szablony z wieloma parametrami
Szablon może także posiadać więcej niż jeden parametr. Na przykład chcielibyśmy napisać klasę Para, zawierającą dwa elementy pierwszy o nazwie pierwszy, a drugi o nazwie drugi, jednakże nie wiemy jakie mają one mieć typ. Możemy to zrobić w ten sposób:
#include <iostream> template <class T1, class T2> class Para { public: Para() { } Para( T1 _a, T2 _b ) : pierwszy(_a), drugi(_b) { } T1 pierwszy; T2 drugi; }; int main(void) { // tworzymy nasz obiekt Para<char*,int> zmienna("Liczba",10); std::cout << zmienna.pierwszy << " " << zmienna.drugi << std::endl; return 0; }
Za pomocą template<class T1, class T2> utworzyliśmy szablon o dwóch parametrach.
[edytuj] Można też liczby
Szablonem może być także liczba. Zilustrujmy to przykładem:
#include <iostream> template <class T, int N> class Tablica { public: Tablica() { } T &operator[]( int i ) { return tabl[i]; } private: T tabl[N]; }; int main(void) { Tablica<int,10> 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; }