C++ szablony (template)
szablon funkcji - jest to ogólny opis funkcji, czyli taki, w którym funkcja jest opisana przez typy ogólne - pod nie można później podstawić typy konkretne.
-> umożliwia programowanie ogólne (generic programming)
void Swap (AnyType & a, AnyType & b)
{
AnyType temp;
temp =a;
a=b;
b=temp;
}
Po co?
Szablon nalezy używać,mkiedy potrzebne nam funkcje stosujące ten sam algorytm do różnych typów danych. Zamiast class należy stosowali raczej typename.
Przykład wykorzystywania:
#include <iostream>
//prototyp
template <typename T>
voidSwap(T & a, T & b);
int main()
{
using namespace;
int i =10, j = 20;
cout << "i, j =" <<i<< " , " <<j<< " .\n";
cout << "Użycie funkcji obsługującej typ int, wygenerowanej przez kompilator. \n";
Swap(i,j) //generuje void Swap (int &, int&)
double x = 24.5, y = 81.7;
cout << "x, y = " << x << ", " << y << ".\n";
cout << "Użycie funkcji obsługującej typ double, wygenerowanej przez kompilator. \n|;
Swap(x,y); //generuje void Swap(double&, double&)
cout << "Teraz x, y= " <<x << ", " << y << ".\n";
//cin.get();
return 0;
}
//def szablonu funkcji
template < typename T>
void Swap (T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
Można dokonać przeciażenia szablonów jak zwykłych funkcji.
Specjalizacje jawne
Można obok szablonu podać specjalizowaną definicję funkcji, która będzie miała potrzebny kod. Funkcja ta będzie wywoływania, gdy typy parametrów wywołane tej funkcji będą odpowiadało typ wykorzystanym do utworzenia definicji tej funkcji.
Jeżeli kompilator znajdzie specjalizowaną definicję pasująca dokładnie do wywołania funkcji użyje właśnie tej definicji nie szukając żadnych szablonów.
Specjalizacja trzeciej generacji (standard C++ ISO/ANSI)
1. Dla danej nazwy funkcji można mieć funkcję nieszablonową z szablon funkcji oraz jawną specjalizację szablonu funkcji oraz przeciążenie wszystkich tych funkcji.
2. Prototyp i definicja jawnej specjalizacji muszą być poprzedzone zapisem template<>, powinny wymieniać nazwę specjalizowanego typu.
3. Specjalizacja przykrywa zwykły szablon, a funkcja zwykła przykrywa je wszystkie.
//prototyp zwykłej funkcji
void Swap (job&, job&);
//szablon prototypu
template <typename T>
void Swap (T&, T&);
/jawna specjalizacja typu job
template <> void Swap <job> (job&, job&);
//lub
template <> void Swap (job&, job&);
Przykład:
#include <iostream>
template <typename T>
void Swap (T& a, T& b);
struct job
{
char name[40];
double salary;
int floor;
}
//jawna specjalizacja
template <> void Swap <job> (job & j1, job & j2);
void show (job & j);
int main ()
{
using namespace std;
cout.precision(2); // precyzja z jaką są liczby wwyświetlane na ekranie
cout.self(ios::fixed, ios::floatfield);
int i = 10, j=20;
cout < "i, j = " <<i << " i " <<j << ".\n";
cout << "Użycie generowanej przez kompilator funkcji zmieniającej wartości int: \n";
Swap (i, j); // generuje void Swap (int & int &)
cout << "Teraz i, j = " << i << ", " << j<< ".\n";
job Sue = { "Susan Yaffee", 73000.60 7};
job Sindey = { "Sidney Taffee", 78060.72, 9};
cout << "Przed zmianą struktur job: \n";
Show(Sue);
Show(Sidney);
Swap (Sue, Sidney); // użycie void Swap (job &, job &)
cout << "Po zmianie struktur job: \n";
Show(Sue);
Show(Sidney);
//cin.get();
return 0;
}
template <typename T>
void Swap (T & a , T & b) // wersja ogólna szablon
{
T temp;
temp = a;
a = b;
b= temp;
}
///specjalizacja zmieniająca tylko pola salary i floor struktury
template <> void Swap<job> (job & j1, job & j2)
{
double t1;
int t2;
t1 = j1. salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}
void Show (job & j)
{
using namespace std;
cout << j.name << ": " << j.salary << " zł na piętrze" < j.floor < endl;
}
Specjalizacje
Włączenie szablonu funkcji nie powoduje wygenerowania definicji funkcji; jest to tylko plan generowania takiej definicji. Kiedy kompilator generuje definicję funkcji dla danego typu na podstawie szablonu, wynikem jest konkretyzacja (czyli utworzenie egzemplarza) szablonu.
np.
int i, j;
Swap (i, j) //kompilator wygeneruje egzeplarz Swap() dla typu int
Szablon nie jest definicję funkcji, natomiast konkretyzacja typu int już jest definicją funkcji.
a) niejawna konkretyzacja
-kompilator "wnioskuje" o konieczności przygotowania definicji po ustaleniu, że program używa funkcji Swap () z parametrami typu int.
b) jawna konkretyzacja
-znaczy, że można jawnie nakazać kompilatorowi utworzenie konkretnego egzemplarza, np Swap<int>()
-składania jest prosta - deklaruje wszystkie potrzebne funkcje,, podając typ w <> i poprzedzając deklarację słowem kluczowym template.
template void Swap<int> (int, int);//"użyj szablonu Swap() do wygenerowania definicji funkcji
//dla typu int"
c) jawna specjalizacji
-różnica pomiędzy jawną konkretyzacją a jawną specjalizacją jest taka, że przy jawnej specjalizacji nie oznacza to użycia szablonu Swap() do wygenerowania definicji funkcji.
-oznacza "użyj odrębnej, wyspecjalizowanej definicji funkcji jawnie zdefiniowanej dla typu int"
-prototypy te muszą być powiązane z odpowiadającymi im definicjami funkcji
-deklaracja jawnej specjalizacji po słowie kluczowym template ma <>, a w jawnej konkretyzacji nawiasów kątowych <> nie ma.
template <> void Swap <int> (int &, int &);
lub
template <> void Swap (int &, int &);
Próba użycia w jednym pliku ( w jednej jednostce translacji) jawnej konkretyzacji i jawnej specjalizacji dla tego samego typu danych jest błędem.
Przykład:
...
template < class T>
void Swap (T &, T&); //prototyp szablonu
template <> void Swap <job> (job &, job&) ; // jawna specjalizacja dla typu job
int main (void)
{
template void Swap <char> (char&, char &); // jawna konkretyzacja dla typu char
short a, b;
...
Swap (a,b); // niejawna konkretyzacja szablonu typu shortjob n, m;
...
Swap(n, m); //użycie jawnej specjalizacji dla typu job
char (g, h);
...
Swap (g, h); // użycie jawnej konkretyzowanego szablonami dla typu char
...
};
Rozwiązywanie Przeciążeń:
Etap 1 - zbieranie listy funkcji potencjalnie przydatnych w danej sytuacji. Wybierane są funkcje i szablony funkcji mające taką samą nazwę jak funkcje wywołane.
Etap 2 - spośród funkcji potencjalnie przydatnych wybierane są funkcje pasujące. Są to funkcje mające odpowiednią liczbę paramterów, dla których istnieją niejawne metody konwersji lub dokładne dopasowanie typu parametrów. Np. jeśli funkcja jest wywołana z parametrem typu float, przekazana wartość zostanie skonwertowana na typ double, aby pasowała do parametru double, ale w przypadku konkretyzacji będzie ono dotyczyć typu float.
Etap 3 - sprawdzenie, czy istnieje funkcja pasująca najlepiej. Jeśli tak, zostanie ona użyta. Jeśli nie, wywołanie zakończy się błędem.
Dopasowania funkcji od najlepszych do najgorszych
1. Dokładnie dopasowaną w tym zwykłe funkcje przed szablonami
2. Konwersje będące proj=mocjami typów (np. automatyczna konwersja typów char i short na typ int oraz float na double).
3. Konwersje standardowe, np. konwersja typu int na char lub typu long na typ double.
4. Konwersja użytkownika, np. zdefiniowane w deklaracjach klas
Przykład - przeciążenie szablonów
#include <iostream>
template <typename T> //szablon A
void ShowArray (Tarr[], int n);
template <typename T> //szablon B
void ShowArray (T* arr[], int n);
struct debts
{
char name[50];
double amount;3;
};
int main()
{
using namespace std;
int things[6] = {13, 31, 103, 301, 310, 130};
struct debts mr_E[3] = {
{"Ima Wolfe", 2400.0},
{"Ura Foxe", 1300.0},
{"Iby Stout", 1800.0}};
double *pd[3]; //deklaracja wskaźnika
//ustawienie wskaźników na pola amount struktur z tablicy Mr_E
for(int i=0; i<3; i++)
{
pd[i]= & mr_E[i].amount
cout << "Wyliczenie długów pana E. : \n";
//things do tablica int
Show Array (things, 6); //używamy szablonu A
cout << "Wyliczenie długów pana E. : \n";
//pd to tablica wskaźników na double
ShowArray(pd, 3); //używamy szablonu B (bardziej wyspecjalizowanego)
return 0;
}
};
template <typename T>
void ShowArray(T arr[], int n)
{
using namespace std;
cout << "Szablon A \n";
for (int i = 0; i<n; i++)
cout << arr[i] << "i";
cout << endl;
}
template <typename T>
void ShowArray(T *arr[], int n)
{
using namespace std;
cout << "Szablon B \n";
for (int i = 0; i<n; i++)
cout << *arr[i] << "i";
cout << endl;
}
Wybór szablonu
#include <iostream>
template <class T>
T lesser (T a, T b) ?? #1
{ return a< b ? a : b ; }
int lesser (int a , int b) // #2
{
a = a < 0 ? - a: a;
b = b < 0 ? - b : b ;
return a < b ? a : b;
}
int main()
{
using namespace;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
cout << lessser(m,n) << endl; //#2
cout << lessser(x,y) << endl; //#1 (z double jakoT)
cout << lessser<>(m,n) << endl; //#1 (z int jako T)
cout << lessser<int>(x,y) << endl; //#1 (z int jako T)
return 0;
}
-> umożliwia programowanie ogólne (generic programming)
void Swap (AnyType & a, AnyType & b)
{
AnyType temp;
temp =a;
a=b;
b=temp;
}
Po co?
Szablon nalezy używać,mkiedy potrzebne nam funkcje stosujące ten sam algorytm do różnych typów danych. Zamiast class należy stosowali raczej typename.
Przykład wykorzystywania:
#include <iostream>
//prototyp
template <typename T>
voidSwap(T & a, T & b);
int main()
{
using namespace;
int i =10, j = 20;
cout << "i, j =" <<i<< " , " <<j<< " .\n";
cout << "Użycie funkcji obsługującej typ int, wygenerowanej przez kompilator. \n";
Swap(i,j) //generuje void Swap (int &, int&)
double x = 24.5, y = 81.7;
cout << "x, y = " << x << ", " << y << ".\n";
cout << "Użycie funkcji obsługującej typ double, wygenerowanej przez kompilator. \n|;
Swap(x,y); //generuje void Swap(double&, double&)
cout << "Teraz x, y= " <<x << ", " << y << ".\n";
//cin.get();
return 0;
}
//def szablonu funkcji
template < typename T>
void Swap (T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
Można dokonać przeciażenia szablonów jak zwykłych funkcji.
Specjalizacje jawne
Można obok szablonu podać specjalizowaną definicję funkcji, która będzie miała potrzebny kod. Funkcja ta będzie wywoływania, gdy typy parametrów wywołane tej funkcji będą odpowiadało typ wykorzystanym do utworzenia definicji tej funkcji.
Jeżeli kompilator znajdzie specjalizowaną definicję pasująca dokładnie do wywołania funkcji użyje właśnie tej definicji nie szukając żadnych szablonów.
Specjalizacja trzeciej generacji (standard C++ ISO/ANSI)
1. Dla danej nazwy funkcji można mieć funkcję nieszablonową z szablon funkcji oraz jawną specjalizację szablonu funkcji oraz przeciążenie wszystkich tych funkcji.
2. Prototyp i definicja jawnej specjalizacji muszą być poprzedzone zapisem template<>, powinny wymieniać nazwę specjalizowanego typu.
3. Specjalizacja przykrywa zwykły szablon, a funkcja zwykła przykrywa je wszystkie.
//prototyp zwykłej funkcji
void Swap (job&, job&);
//szablon prototypu
template <typename T>
void Swap (T&, T&);
/jawna specjalizacja typu job
template <> void Swap <job> (job&, job&);
//lub
template <> void Swap (job&, job&);
Przykład:
#include <iostream>
template <typename T>
void Swap (T& a, T& b);
struct job
{
char name[40];
double salary;
int floor;
}
//jawna specjalizacja
template <> void Swap <job> (job & j1, job & j2);
void show (job & j);
int main ()
{
using namespace std;
cout.precision(2); // precyzja z jaką są liczby wwyświetlane na ekranie
cout.self(ios::fixed, ios::floatfield);
int i = 10, j=20;
cout < "i, j = " <<i << " i " <<j << ".\n";
cout << "Użycie generowanej przez kompilator funkcji zmieniającej wartości int: \n";
Swap (i, j); // generuje void Swap (int & int &)
cout << "Teraz i, j = " << i << ", " << j<< ".\n";
job Sue = { "Susan Yaffee", 73000.60 7};
job Sindey = { "Sidney Taffee", 78060.72, 9};
cout << "Przed zmianą struktur job: \n";
Show(Sue);
Show(Sidney);
Swap (Sue, Sidney); // użycie void Swap (job &, job &)
cout << "Po zmianie struktur job: \n";
Show(Sue);
Show(Sidney);
//cin.get();
return 0;
}
template <typename T>
void Swap (T & a , T & b) // wersja ogólna szablon
{
T temp;
temp = a;
a = b;
b= temp;
}
///specjalizacja zmieniająca tylko pola salary i floor struktury
template <> void Swap<job> (job & j1, job & j2)
{
double t1;
int t2;
t1 = j1. salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}
void Show (job & j)
{
using namespace std;
cout << j.name << ": " << j.salary << " zł na piętrze" < j.floor < endl;
}
Specjalizacje
Włączenie szablonu funkcji nie powoduje wygenerowania definicji funkcji; jest to tylko plan generowania takiej definicji. Kiedy kompilator generuje definicję funkcji dla danego typu na podstawie szablonu, wynikem jest konkretyzacja (czyli utworzenie egzemplarza) szablonu.
np.
int i, j;
Swap (i, j) //kompilator wygeneruje egzeplarz Swap() dla typu int
Szablon nie jest definicję funkcji, natomiast konkretyzacja typu int już jest definicją funkcji.
a) niejawna konkretyzacja
-kompilator "wnioskuje" o konieczności przygotowania definicji po ustaleniu, że program używa funkcji Swap () z parametrami typu int.
b) jawna konkretyzacja
-znaczy, że można jawnie nakazać kompilatorowi utworzenie konkretnego egzemplarza, np Swap<int>()
-składania jest prosta - deklaruje wszystkie potrzebne funkcje,, podając typ w <> i poprzedzając deklarację słowem kluczowym template.
template void Swap<int> (int, int);//"użyj szablonu Swap() do wygenerowania definicji funkcji
//dla typu int"
c) jawna specjalizacji
-różnica pomiędzy jawną konkretyzacją a jawną specjalizacją jest taka, że przy jawnej specjalizacji nie oznacza to użycia szablonu Swap() do wygenerowania definicji funkcji.
-oznacza "użyj odrębnej, wyspecjalizowanej definicji funkcji jawnie zdefiniowanej dla typu int"
-prototypy te muszą być powiązane z odpowiadającymi im definicjami funkcji
-deklaracja jawnej specjalizacji po słowie kluczowym template ma <>, a w jawnej konkretyzacji nawiasów kątowych <> nie ma.
template <> void Swap <int> (int &, int &);
lub
template <> void Swap (int &, int &);
Próba użycia w jednym pliku ( w jednej jednostce translacji) jawnej konkretyzacji i jawnej specjalizacji dla tego samego typu danych jest błędem.
Przykład:
...
template < class T>
void Swap (T &, T&); //prototyp szablonu
template <> void Swap <job> (job &, job&) ; // jawna specjalizacja dla typu job
int main (void)
{
template void Swap <char> (char&, char &); // jawna konkretyzacja dla typu char
short a, b;
...
Swap (a,b); // niejawna konkretyzacja szablonu typu shortjob n, m;
...
Swap(n, m); //użycie jawnej specjalizacji dla typu job
char (g, h);
...
Swap (g, h); // użycie jawnej konkretyzowanego szablonami dla typu char
...
};
Rozwiązywanie Przeciążeń:
Etap 1 - zbieranie listy funkcji potencjalnie przydatnych w danej sytuacji. Wybierane są funkcje i szablony funkcji mające taką samą nazwę jak funkcje wywołane.
Etap 2 - spośród funkcji potencjalnie przydatnych wybierane są funkcje pasujące. Są to funkcje mające odpowiednią liczbę paramterów, dla których istnieją niejawne metody konwersji lub dokładne dopasowanie typu parametrów. Np. jeśli funkcja jest wywołana z parametrem typu float, przekazana wartość zostanie skonwertowana na typ double, aby pasowała do parametru double, ale w przypadku konkretyzacji będzie ono dotyczyć typu float.
Etap 3 - sprawdzenie, czy istnieje funkcja pasująca najlepiej. Jeśli tak, zostanie ona użyta. Jeśli nie, wywołanie zakończy się błędem.
Dopasowania funkcji od najlepszych do najgorszych
1. Dokładnie dopasowaną w tym zwykłe funkcje przed szablonami
2. Konwersje będące proj=mocjami typów (np. automatyczna konwersja typów char i short na typ int oraz float na double).
3. Konwersje standardowe, np. konwersja typu int na char lub typu long na typ double.
4. Konwersja użytkownika, np. zdefiniowane w deklaracjach klas
Przykład - przeciążenie szablonów
#include <iostream>
template <typename T> //szablon A
void ShowArray (Tarr[], int n);
template <typename T> //szablon B
void ShowArray (T* arr[], int n);
struct debts
{
char name[50];
double amount;3;
};
int main()
{
using namespace std;
int things[6] = {13, 31, 103, 301, 310, 130};
struct debts mr_E[3] = {
{"Ima Wolfe", 2400.0},
{"Ura Foxe", 1300.0},
{"Iby Stout", 1800.0}};
double *pd[3]; //deklaracja wskaźnika
//ustawienie wskaźników na pola amount struktur z tablicy Mr_E
for(int i=0; i<3; i++)
{
pd[i]= & mr_E[i].amount
cout << "Wyliczenie długów pana E. : \n";
//things do tablica int
Show Array (things, 6); //używamy szablonu A
cout << "Wyliczenie długów pana E. : \n";
//pd to tablica wskaźników na double
ShowArray(pd, 3); //używamy szablonu B (bardziej wyspecjalizowanego)
return 0;
}
};
template <typename T>
void ShowArray(T arr[], int n)
{
using namespace std;
cout << "Szablon A \n";
for (int i = 0; i<n; i++)
cout << arr[i] << "i";
cout << endl;
}
template <typename T>
void ShowArray(T *arr[], int n)
{
using namespace std;
cout << "Szablon B \n";
for (int i = 0; i<n; i++)
cout << *arr[i] << "i";
cout << endl;
}
Wybór szablonu
#include <iostream>
template <class T>
T lesser (T a, T b) ?? #1
{ return a< b ? a : b ; }
int lesser (int a , int b) // #2
{
a = a < 0 ? - a: a;
b = b < 0 ? - b : b ;
return a < b ? a : b;
}
int main()
{
using namespace;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
cout << lessser(m,n) << endl; //#2
cout << lessser(x,y) << endl; //#1 (z double jakoT)
cout << lessser<>(m,n) << endl; //#1 (z int jako T)
cout << lessser<int>(x,y) << endl; //#1 (z int jako T)
return 0;
}
Komentarze
Prześlij komentarz