Wykład 8
Wzorce (szablony)

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.

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