Wzorce funkcji
Załóżmy, że piszemy program, w którym wielokrotnie wybieramy z dwóch liczb całkowitych tą mniejszą. Naturalne zatem staje się wprowadzenie takiej funkcji:
int min(int a, int b)
{
if(a<b)
return a;
return b;
}
|
|
Listing 8.1
|
Załóżmy jednak dalej, że nasz program rozbudowaliśmy i zachodzi potrzeba wybierania mniejszej z dwóch liczb typu
float. Cóż, należy zatem napisać
identycznie wyglądającą funkcję, ale różniącą się typem użytych zmiennych:
float min(float a, float b)
{
if(a<b)
return a;
return b;
}
|
|
Listing 8.2
|
Jeśli dalej zajdzie konieczność użycia funkcji
min dla typu
double to znowu należy przekopiować odpowiedni fragment kodu i zmienic typu. Właśnie:
przwkopiować i zmienić - czynność bardzo mechaniczna. Czy nie może robić tego za nas komputer? Może :))) Otóż możemy napisać tak zwany
szablon funkcji, który będzie niejako pieczątką do robienia funkcji. W naszym przypadku szablon ten wyglądałby tak
template <class typ>
typ min(typ a, typ b)
{
if(a<b)
return a;
return b;
}
|
|
Listing 8.3
|
Zobaczmy zatem jak mógłby wyglądać gotowy program
#include <stdio.h>
template <class typ>
typ min(typ a, typ b)
{
int static licznik=1;
printf("min dla typu o rozmiarze %d; licznik=%d ",sizeof(typ),licznik);
licznik++;
if(a<b)
return a;
return b;
}
int main()
{
int i1=7, i2=3;
float f1=7, f2=3;
double d1=7, d2=3;
printf("%d\n",min(i1,i2));
printf("%f\n",min(i1,i2));
printf("%d\n",min(f1,f2));
printf("%f\n",min(f1,f2));
printf("%d\n",min(d1,d2));
printf("%f\n",min(d1,d2));
return 0;
}
|
|
Listing 8.4
|
W celach "edukacyjnych" użyłem funkcji
printf w
main aby pokazać, że faktycznie otrzymamy 3 różne funkcje. Wynik działania przedstawiam poniżej
min dla typu o rozmiarze 4; licznik=1 3
min dla typu o rozmiarze 4; licznik=2 0.000000
min dla typu o rozmiarze 4; licznik=1 0
min dla typu o rozmiarze 4; licznik=2 3.000000
min dla typu o rozmiarze 8; licznik=1 0
min dla typu o rozmiarze 8; licznik=2 3.000000
|
|
Output 8.1
Efekt działania programu z listingu 8.4
|
Jak widać pierwszya funkcja faktycznie działa na
int-tach, gdyż próba interpretacji zwacanego wyniku jako
float deje błędny rezultat. Także drugie i trzecie wywołanie są dla różnych funkcji - widać to po różneh wielkości użytych zmiennych.
- Szablon pozwala na tworzenie podobnych funkcji, identycznych w działaniu, ale różniących się typem argumentów.
- Decyzja o wyborze odpowiedniej funkcji szablonowej podejmowana jest na podstawie typów argumentów z wywołania funkcji; nie jest istoty typ zmiennej do której przypisujemy zawracaną wartość. Spójrzmy poniżej
#include <stdio.h>
template <class typ>
typ min(typ a, typ b)
{
int static licznik=1;
printf("min dla typu o rozmiarze %d; licznik=%d\n",sizeof(typ),licznik);
licznik++;
if(a<b)
return a;
return b;
}
int main()
{
int i1=7, i2=3, x;
float y;
double z;
x=min(i1,i2);
y=min(i1,i2);
z=min(i1,i2);
return 0;
}
|
|
Listing 8.5
|
min dla typu o rozmiarze 4; licznik=1
min dla typu o rozmiarze 4; licznik=2
min dla typu o rozmiarze 4; licznik=3
|
|
Output 8.2
Efekt działania programu z listingu 8.5
|
- Szablon musi być zdefiniowany w zakresie globalnym.
- Oczywiście nic nie stoi na przeszkodzie aby w szablonie użyć więcej niż jeden parametr (patrz przykład poniżej).
- Jeśli funkcja szablonowa posiada więcej niż jeden argument tego samego parametryzowalnego typu, to w liście parametrów szablonu nazwa tego typu występuje tylko raz (patrz przykład poniżej).
-
template <class typ1, class typ2, class typ3>
typ1 NazwaFunkcji(typ1 a, typ2 b, typ2 c, typ2 d, typ3 e);
|
|
Listing 8.6
|
- Nazwa typu będącego parametrem szablonu musi się pojawić w sygnaturze funkcji, czyli musi wystąpić jako typ argumentu funkcji.
- Parametrem szablonu funkcji nie może być typ rezultatu (jeśli ten typ jest odmienny od wszystkich ewentualnych argumentów funkcji). Czyli nie można napisać
template <class typ1, class typ2>
typ1 NazwaFunkcji(typ2 x);
|
|
Listing 8.7
|
Wzorce klas
Ostatecznie bez szablonów w przypadku funkcji jakoś można sobie poradzić. Wyobraźmy sobie jednak, że w pocie czoła napisaliśy klasę kolejka dla typu int. Jest to doskonała kolejka, możemy z nią zrobić wszystko (co oczywiście da się zrobić z kolejką :)) ). Ma być jednokierunkowa? Jest. Dwukierunkowa? Bardzo prosze. Działać jak stos? Nic trudnego. Działać dla typu float? Hmm... Nie bardzo. To znaczy, jest to możliwe, ale... Trzeba skopiować i poprawić strasznie dużo linii kodu. Nie muszę chyba przypominać o prawdopodobieństwie popełnienia błędu? :))) Właśnie w takich sytuacjach najbardziej przydaje się mechanizm szablonów, który na szczęście może przyjść nam tu z pomocą. W przypadku szablonów klas będziemy postępować podobnie jak dla szablonów funkcji. Zresztą popatrzmy na poniższy program
#include <stdio.h>
template <class typ>
class test
{
public:
static int licznik;
typ x;
test();
void SetX(typ t){x=t;};
typ GetX(){return x;};
void Wypisz();
};
template <class typ>
test<typ>::test()
{
printf("Konstruktor dla typu o rozmiarze %d; licznik=%d\n",sizeof(typ),licznik);
x=0;
licznik++;
}
template <class typ>
void test<typ>::Wypisz()
{
printf("Typ o rozmiarze %d; licznik=%d\n",sizeof(typ),licznik);
}
template<class typ>
int test<typ>::licznik=5;
int main()
{
test<float> i1, i2, i3;
test<double> f1, f2, f3;
printf("%f\n",i1.GetX());
printf("%f\n",i2.GetX());
printf("%f\n",i3.GetX());
i2.SetX(3);
printf("%f\n",i1.GetX());
printf("%f\n",i2.GetX());
printf("%f\n",i3.GetX());
printf("-------------\n");
printf("%f\n",f1.GetX());
printf("%f\n",f2.GetX());
printf("%f\n",f3.GetX());
f1.SetX(3);
printf("%f\n",f1.GetX());
printf("%f\n",f2.GetX());
printf("%f\n",f3.GetX());
return 0;
}
|
|
Listing 8.8
|
Konstruktor dla typu o rozmiarze 4; licznik=5
Konstruktor dla typu o rozmiarze 4; licznik=6
Konstruktor dla typu o rozmiarze 4; licznik=7
Konstruktor dla typu o rozmiarze 8; licznik=5
Konstruktor dla typu o rozmiarze 8; licznik=6
Konstruktor dla typu o rozmiarze 8; licznik=7
0.000000
0.000000
0.000000
0.000000
3.000000
0.000000
-------------
0.000000
0.000000
0.000000
3.000000
0.000000
0.000000
|
|
Output 8.3
Efekt działania programu z listingu 8.8
|