Dr hab. inż. Lucyna Leniowska, prof. UR, Zakład Mechatroniki, Automatyki i Optoelektroniki, IT Programowanie obiektowe, wykład nr 10 Metaprogramowanie cz.2: szablony struktur i klas Definiowanie szablonów struktur i klas Definiując szablony funkcji pozostawiamy kompilatorowi języka C++ utworzenie funkcji dla różnych typów parametrów lub różnych typów zwracanych wartości. Przy użyciu słowa kluczowego template i symboli typów, takich jak T, T1 i T2 można w języku C++ tworzyć szablony struktur i klas. Pierwszym słowem jest template, czyli szablon. Następnie w trójkątnych nawiasach umieszcza się słowo typename ( zamiennie struct lub class) - które może oznaczać dowolny typ danych. Szablon struktury Załóżmy, że tworzymy struktury dla par danych reprezentujących różne typy. Bez szablonu wygląda to tak: #include <iostream> struct para_int_int int first; int second; para_int_int(int fst, int snd) : first(fst), second(snd) ; struct para_double_double double first; double second; para_double_double(double fst, double snd) : first(fst), second(snd) ;
struct para_string_int std::string first; int second; para_string_int(std::string fst, int snd) : first(fst), second(snd) ; para_int_int f1() return para_int_int(1, 2); para_double_double f2() return para_double_double(3.14, 2.71); para_string_int f3() return para_string_int("janek", 28); void wypisz(para_int_int argument) std::cout << "Para (" << argument.first << ", " << argument.second << ")" << std::endl; void wypisz(para_double_double argument) std::cout << "Para (" << argument.first << ", " << argument.second << ")" << std::endl; void wypisz(para_string_int argument) std::cout << "Para (" << argument.first << ", " << argument.second << ")" << std::endl; int main() wypisz(f1()); wypisz(f2()); wypisz(f3()); return 0; Ne ekranie zobaczymy: Para (1, 2) Para (3.14, 2.71) Para (Janek, 28) Utwórzmy szablon struktury Aby zwiększyć możliwości ponownego wykorzystywania gotowego kodu, język C++ pozwala definiować w programach szablony struktur. Szablon tworzący uniwersalną strukturę para:
template <typename T1, typename T2> struct para T1 first; T2 second; para(t1 fst, T2 snd) : first(fst), second(snd) ; Ten szablon definiuje symbole typu T1 i T2. Dla struktury par liczb całkowitych int_int T1 i T2 będzie odpowiadać int. Analogicznie dla struktury par liczb rzeczywistych T1 i T2 będzie to typ float. Dla struktury par string_ int T1 będzie odpowiadać string, a T2 int. Następnie, aby zdefiniować funkcje struktur : przed każdą funkcją struktury należy podać ten sam nagłówek szablonu, bezpośrednio za nazwą struktury należy podać typy, np. para<t1,t2> oraz definicję funkcji. Poniższe instrukcje przedstawiają definicję funkcji f1, f2, f3, które ustawiają wartości pól struktury. para<int,int> f1() return para<int,int>(1, 2); para<double,double> 2.71); f2() return para<double,double>(3.14, para<std::string,int> f3() return para<std::string,int>("janek", 28); Przykład 10.1. Przedstawiony poniżej program przy użyciu szablonu struktury para wyświetla pary danych różnych typów.
#include <iostream> template < typename T1, typename T2> struct para T1 first; T2 second; para(t1 fst, T2 snd) : first(fst), second(snd) ; para<int,int> f1() return para<int,int>(1, 2); para<double,double> 2.71); f2() return para<double,double>(3.14, para<std::string,int> f3() return para<std::string,int>("janek", 28); template < typename T1, typename T2> void wypisz(para<t1,t2> argument) std::cout << "Para (" << argument.first << ", " << argument.second << ")" << std::endl; int main() wypisz(f1()); wypisz(f2()); wypisz(f3()); return 0; Szablon klasy Załóżmy, że tworzymy klasę dla tablic, która definiuje metody służące do obliczania sumy i średniej dla elementów przechowywanych w tablicy. Jeśli przyjmiemy, że będziemy operować na tablicach typu int, to otrzymamy następującą definicję klasy: class tablica public: tablica (int rozmiar); long suma (void); int srednia (void); void wypisz_tablice (void); int dodaj_element (int); private:
; int *dane; int rozmiar; int indeks; Przykład 10.2. Przedstawiony poniżej program przy użyciu klasy tablica operuje na wartościach typu int. #include <iostream> #include <stdlib.h> class tablica public: tablica (int rozmiar); long suma (void); int srednia (void); void wypisz_tablice (void); int dodaj_element (int); private: int *dane; int rozmiar; int indeks; ; tablica::tablica (int rozmiar) dane = new int[rozmiar]; if (dane == NULL) std::cerr << "Brak pamięci--zakończenie programu" << std::endl; exit (1); tablica::rozmiar = rozmiar; tablica::indeks = 0; long tablica::suma(void) long suma = 0;
suma += dane[i]; return (suma); int tablica::srednia(void) long suma = 0; suma += dane[i]; return (suma / indeks); void tablica::wypisz_tablice (void) std::cout << dane[i] << ' '; std::cout << std::endl; int tablica::dodaj_element (int liczba) if (indeks == rozmiar) return (-1);//Tablica jest zapełniona else dane[indeks] = liczba; indeks++; return(0); //Poprawna operacja int main(void) tablica liczby(100); //Tablica 100 elementowa for (i = 0; i < 50; i++) liczby.dodaj_element(i); liczby.wypisz_tablice();
std::cout << "Suma liczb w tablicy wynosi " << liczby.suma() << std::endl; std::cout << "Średnia wartości liczb w tablicy wynosi " << liczby.srednia() << std::endl; return 0; Program tworzy tablicę 100-elementową, a następnie przypisuje wartości jej 50 elementom przy użyciu metody dodaj_element. Program przydziela (alokuje) pamięć dla elementów tablicy przy użyciu operatora new. Zastosowanie szablonu klasy Szablon klasy definiuje uniwersalną klasę, dla której można później utworzyć obiekt odpowiedniego typu. Załóżmy, że program musi operować również na tablicach typu float. Można zatem utworzyć dla tego typu wartości nową klasę. Jednak powtarzanie kodu klasy nie będzie konieczne, jeśli użyjemy szablonu klasy. Szablon tworzący uniwersalną klasę tablica: class tablica public: tablica (int rozmiar); ~tablica(); T1 suma (void); T srednia (void); void wypisz_tablice (void); int dodaj_element (T); private: T *dane; int rozmiar; int indeks; ; Ten szablon definiuje symbole typu T i T1. Dla tablicy całkowitej T będzie odpowiadać int, a T1 long. Analogicznie dla tablicy liczb rzeczywistych T i T1 to będzie typ float. Następnie, aby zdefiniować metody/funkcje klasy należy: przed każdą funkcją klasy należy podać ten sam nagłówek szablonu, bezpośrednio za nazwą klasy należy podać typy, np. tablica<t,t1>::srednia.
Poniższe instrukcje przedstawiają definicję funkcji srednia: T tablica<t, T1>::srednia(void) T1 suma = 0; suma += dane[i]; return (suma / indeks); Deklarowanie obiektów w oparciu o szablon klasy W celu zadeklarowania obiektów przy użyciu szablonu klasy musimy podać nazwę szablonu klasy, a za nią w nawiasach kątowych typy, które kompilator ma wstawić w miejsce symboli T, T1, T2 itd. Następnie należy określić nazwę obiektu (zmiennej) i parametry, które powinien przekazać funkcji konstruktor: nazwa_szablonu_klasy<typ1, typ2> nazwa_obiektu (parametr1, parametr2); Gdy kompilator C++ napotka taką deklarację, to utworzy obiekt nazwa_obiektu w oparciu o podane typy i szablon nazwa_szablonu_klasy. Na przykład poniższa deklaracja tworzy przy użyciu szablonu klasy tablica obiekt wartości: tablica<float, float> wartosci(200); Poniższa deklaracja tworzy przy użyciu szablonu klasy tablica tablicę typu char male_liczby, która ma przechowywać 100 elementów: tablica<char, int> male_liczby(100); Przykład 10.3. Przedstawiony poniżej program przy użyciu szablonu klasy tablica tworzy dwie klasy: jedną dla tablic typu int i drugą dla tablic typu float.
#include <iostream> #include <stdlib.h> class tablica public: tablica (int rozmiar); ~tablica(); T1 suma (void); T srednia (void); void wypisz_tablice (void); int dodaj_element (T); private: T *dane; int rozmiar; int indeks; ; tablica<t, T1>::tablica (int rozmiar) dane = new T[rozmiar]; if (dane == NULL) std::cerr << "Brak pamięci--zakończenie programu" <<std::endl; exit (1); tablica::rozmiar = rozmiar; tablica::indeks = 0; tablica<t, T1>::~tablica () delete[] dane; T1 tablica<t, T1>::suma(void) T1 suma = 0; suma += dane[i]; return (suma);
template <class T, class T1> T tablica<t, T1>::srednia(void) T1 suma = 0; suma += dane[i]; return (suma / indeks); void tablica<t, T1>::wypisz_tablice (void) std::cout << dane[i] << ' '; std::cout << std::endl; int tablica<t, T1>::dodaj_element (T liczba) if (indeks == rozmiar) return (-1);//Tablica jest zapełniona else dane[indeks] = liczba; indeks++; return(0); //Poprawna operacja int main(void) tablica <int, long>liczby(100); tablica <float, float>wartosci(200); //Tablica 100 elementowa //Tablica 200 elementowa for (i = 0; i < 50; i++) liczby.dodaj_element(i); liczby.wypisz_tablice();
std::cout << "Suma liczb w tablicy wynosi " << liczby.suma() << std::endl; std::cout << "Średnia wartości liczb w tablicy wynosi " << liczby.srednia() << std::endl; for (i = 0; i < 100; i++) wartosci.dodaj_element(i); wartosci.wypisz_tablice(); std::cout << "Suma liczb w tablicy wynosi " << wartosci.suma() << std::endl; std::cout << "Średnia wartość liczb w tablicy wynosi " << wartosci.srednia() << std::endl; return 0; Na ekranie zobaczymy: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 Suma liczb w tablicy wynosi 1225 Średnia wartości liczb w tablicy wynosi 24 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 Suma liczb w tablicy wynosi 4950 Średnia wartość liczb w tablicy wynosi 49.5 Podsumowanie informacji o szablonach struktur i klas Przy użyciu szablonów struktur i klas można w programach eliminować powtarzanie kodu dla struktur i klas. Aby utworzyć szablon struktury/klasy, należy poprzedzić definicję słowem kluczowym template i zastosować symbole typów, np. T i T1. Następnie należy poprzedzić definicje wszystkich struktur/klas tą samą instrukcją template. Ponadto w nawiasach kątowych należy podać typy szablonu pomiędzy nazwą klasy i nazwą funkcji, np. nazwa_klasy<t, T1>::nazwa_funkcji.
Aby utworzyć strukturę/klasę na podstawie szablonu, podajemy jej nazwę i w nawiasach trójkątnych aktualne typy danych np. nazwa_klasy<int, long> oraz nazwę funkcji. W celu utworzenia obiektów klasy przy użyciu szablonu klasy należy podać w programie nazwę klasy, typy, które kompilator ma wstawić w miejsce typów uniwersalnych umieszczone w nawiasach kątowych (np. <int, float> ) i nazwę zmiennej/obiektu. Jeśli struktura/klasa ma zdefiniowany konstruktor, przy użyciu którego można nadawać wartości początkowe składowym klasy, to można go wywoływać przy tworzeniu obiektu na podstawie szablonu struktury/klasy, np. nazwa_klasy<int, float> obiekt(200);.