Szablony klas i funkcji
Przepisz poniższy fragment kodu, zawierający cztery definicje funkcji max, używając szablonu funkcji, tak by zamiast czterech definicji mieć jedną
#include <iostream.h>
int max(int a, int b);
char max (char a, char b);
float max (float a, float b);
bool max (bool a, bool b);
void main()
{
int i1=1,i2=2;
char c1='S', c2='T';
float f1=1.2, f2=0.3;
bool b1=true, b2=false;
cout << "Maksimum z int: "<< max (i1,i2) << endl;
cout << "Maksimum z char: "<< max (c1,c2) << endl;
cout << "Maksimum z flaot: "<< max (f1,f2) << endl;
cout << "Maksimum z bool: "<< max (b1,b2) << endl;
}
int max(int a, int b)
{
if(a>b) return a;
else return b;
}
char max (char a, char b)
{
if(a>b) return a;
else return b;
}
float max (float a, float b)
{
if(a>b) return a;
else return b;
}
bool max (bool a, bool b)
{
if(a>b) return a;
else return b;
}
Rozwiązanie:
#include <iostream.h>
template <typename dowolny_typ >
dowolny_typ max(dowolny_typ a, dowolny_typ b)
{
if(a>b) return a;
else return b;
}
void main()
{
int i1=1,i2=2;
char c1='S', c2='T';
float f1=1.2, f2=0.3;
bool b1=true, b2=false;
cout << "Maksimum z bool: "<< max(b1,b2) << endl;
cout << "Maksimum z int: "<< ::max(i1,i2) << endl;
cout << "Maksimum z flaot: "<< ::max<>(f1,f2) << endl;
cout << "Maksimum z char: "<< ::max<int>(c1,c2)
<< endl;
}
Wynik wykonania:
Maksimum z bool: 1
Maksimum z int: 2
Maksimum z flaot: 1.2
Maksimum z char: 84
Zgodnie z tym co oczekiwano otrzymano kolejno maksymalne wartości:
1 ze zmiennych bool (true, false),
2 ze zmiennych int (1,2),
1.2 ze zmiennych flaot (1.2, 0.2),
84 ze zmiennych (`S', `T') ponieważ skonkretyzowano funkcje max dla int.
Słowo kluczowe template informuje kompilator, że dokonamy definicji szablonu funkcji. max. Po słowie kluczowym typename podajemy nazwy parametrów używanych w szablonie (słowo kluczowe typename może być zastąpione słowem class, są one wymienne)...
... ciąg dalszy definicji szablonu funkcji max. Będzie ona przyjmować dwa argumenty a i b o dowolnym typie (dowolny_typ), zaś będzie zwracać typ taki jak typ parametrów;
wywołana jest funkcja max z parametrami typu bool, czyli następuje konkretyzacja funkcji z parametrami bool;
w dalszym ciągu kompilator dedukuje typ zmiennych i na tej podstawie konkretyzuje funkcje;
dodanie do wywołania funkcji dodatkowych znaków ::max<> wskazuje również aby samodzielnie wydedukował typ zmiennych;
istnieje możliwość podania kompilatorowi w jaki sposób ma być skonkretyzowana funkcja. Powyżej nakazano aby parametry potraktować jak zmienne typu char. Stad zmienne char zostały zrzutowane na int.
W ćwiczeniu tym zostanie przeprowadzony eksperyment, który będzie miał na celu określenie kiedy jest używany szablon a kiedy zwykła funkcja. W tym celu należy zdefiniować szablon i funkcje o takiej samej nazwie i takiej samej liście argumentów, a następnie wywołać ją i sprawdzić, która wersja się uruchomiła.
#include <string>
#include <iostream>
template<typename T> // Definicja szablonu
std::string funkcja(T){
return “Wita szablon!”;
}
std::string f(int&){ // Definicja zwyklej funkcji
return “Wita funkcja!”;
}
int main(){
int x = 7; // Parametr dla funkcji
std::cout<< f(x) << std::endl; // Wywolanie funkcji
};
Zdefiniuj szablon funkcji do obliczania minimum z dwóch liczb.
Zdefiniuj szablon funkcji do obliczania maksimum z dwóch liczb. Jednak tym razem liczby te mogą być różnego typu, tak by było możliwe wpisanie np. max(1, `s'), albo max(3.4 , true). Dla uproszczenia niech typem zwracanym będzie liczba całkowita 1, jeśli pierwszy argument jest większy od drugiego, 0 - w przeciwnym wypadku.
template <typename typ1, typename typ2 >
int max(typ1 a, typ2 b)
{
if(a>b) return 1;
else return 0;
}
Zdefiniuj szablon funkcji typu inline dla utworzonej poprzednio funkcji max.
template <typename typ >
inline typ max(typ a, typ b)
{
if(a>b) return a;
else return b;
}
Jeśli chcielibyśmy utworzyć funkcje z przydomkiem static lub extern to słówko to wpisalibyśmy w miejscu słowa inline.
Zdefiniuj szablon funkcji do obliczania sumy dwóch liczb. Jednak liczby te mają być różnego typu, tak by było możliwe wpisanie np. suma(1, `S'), albo suma(3.4 , true). Dodatkowo niech będzie możliwe zdefiniowanie typu rezultatu sumy (sic!)
template <typename typ1, typename typ2, typename typ_rezultatu >
typ_rezultatu suma(typ1 a, typ2 b, typ_rezultatu)
{
return (a+b);
}
Użycie:
cout << suma(1, `S', (char)1) << endl;
cout << suma(3.4, 3, (float)4) << endl;
Wynik wykonania:
T
6.4
Zdefiniuj szablon funkcji max jak w poprzednich ćwiczeniach, jednak tym razem należy dodać taką regułę przy porównywaniu łańcuchów znaków: „większy łańcuch to ten, który ma większą liczbę znaków”.
#include <iostream.h>
#include <string.h>
template <typename dowolny_typ >
dowolny_typ max(dowolny_typ a, dowolny_typ b)
{
if(a>b)
return a;
else
return b;
}
char* max(char* a, char* b)
{
if(strlen(a)>strlen(b)) return a;
else return b;
}
void main()
{
int i1=2, i2=5;
char s1[]=”olo”, s2[]=”bardzo długi napis..”;
cout << max(i1, i2) << endl
<< max(s1, s2) << endl;
}
Napisz funkcje do sortowania (np. metodą bąbelkową) dowolnych danych.
// Szablon funkcji do sorotowania
template <class T> void sortuj (T *w, const int ile);
void main()
{
int t1[6] ={13,21,5,0,-2,3};
float t2[6] = {0.2, -1.1, 4.6, 3.1, 1.0, 3.3};
sortuj(t1, 6); //wywołanie funkcji dla elementów typu int
sortuj(t2, 6); //wywołanie funkcji dla elementów typu float
}
// Definicja szablonu
template <class T> void sortuj (T *w, const int ile)
{
for (int i= 0; i < ile-1; i++)
for (int j=0; j <ile-1-i; j++)
if (w[j] > w[j+1])
{ T pom = w[j];
w[j] = w[j+1];
w[j+1]= pom;
}
}
Zdefiniuj klasę Schowek do przechowywania zmiennej dowolnego typu o nazwie zawartość. Dodatkowo można zdefiniować dla tej klasy konstruktor i funkcję zwracającą zawartość schowka. Definicja konstruktora i destruktora niech będzie w ciele klasy.
#include <iostream.h>
template <class T>
class Schowek {
T zawartosc;
public:
Schowek(T zaw) {zawartosc = zaw;}
T pobierz() {return zawartosc;}
};
void main(int argc, char* argv[])
{
Schowek<int> s1(10);
Schowek<char> s2('s');
cout << s1.pobierz() << endl
<< s2.pobierz() << endl;
}
Wynik wykonania:
10
s
1 - 2: definicja szablonu klasy, poszczególne klasy utworzone na podstawie tej klasy będą się różnić typem argumentu T.
3 - 5: …cd. definicji szablonu klasy Schowek ze zmienną zawartosc dowolnego typu oraz konstruktorem i metodą pobierz().
6: utworzenie obiektu klasy Schowek o nazwie s1. Jest on utworzony na podstawie szablonu klasy Schowek. Konkretyzujemy, że ma on mieć zawartosc typu int. Dodatkowo inicjalizujemy go wartością 10.
11: utworzenie obiektu klasy Schowek o nazwie s2. Konkretyzujemy, że ma on mieć zawartosc typu char. Dodatkowo inicjalizujemy go wartością `s'.
8: sprawdzenie czy rzeczywiście obiekty zawierają zmienne podanego typu. Potwierdzają to wyniki, kolejno:
10
s
Zdefiniuj klasę Schowek jak w poprzednim ćwiczeniu. Jednak tym razem definicja konstruktora i destruktora niech będą poza ciałem klasy.
#include <iostream.h>
template <class T>
class Schowek {
T zawartosc;
public:
Schowek(T zaw);
T pobierz();
};
template <class T>
Schowek<T>::Schowek(T zaw){
zawartosc = zaw;
}
template <class T>
T Schowek<T>::pobierz(){
return zawartosc;
}
void main(int argc, char* argv[])
{
Schowek<int> s1(10);
Schowek<char> s2('s');
cout << s1.pobierz() << endl
<< s2.pobierz () << endl;
}
Wynik wykonania:
10
s
1: rozpoczęcie definicji szablonu klasy, poszczególne klasy utworzone na podstawie tej klasy będą się różnić typem argumentu T…
2 - 5: …cd. definicji szablonu klasy Schowek ze zmienną zawartosc oraz konstruktorem i metodą pobierz().
6 - 7: definiujemy szablon konstruktora dla klasy Schowek. Zapis Schowek<T>:: informuje kompilator, że ma do czynienia z definicją składnika szablonu klasy, czytając dalej Schowek(T zaw) ”dowie się”, że jest to konstruktor, przyjmujący jeden argument typu T.
8, 9: podobnie przy definicji funkcji pobierz w linii 8,9.
10: utworzenie obiektu klasy Schowek o nazwie s1. Jest on utworzony na podstawie szablonu klasy Schowek. Konkretyzujemy, że ma on mieć zawartosc typu int. Dodatkowo inicjalizujemy go wartością 10.
11: utworzenie obiektu klasy Schowek o nazwie s2. Konkretyzujemy, że ma on mieć zawartosc typu char. Dodatkowo inicjalizujemy go wartością `s'.
12: sprawdzenie czy rzeczywiście obiekty zawierają zmienne podanego typu. Potwierdzają to wyniki, kolejno:
10
s
Utwórz obiekty klasy Schowek do przechowywania:
liczb całkowitych,
znaku,
wskaźnika na liczby całkowite,
tablicy pięciu liczb rzeczywistych,
klasy Schowek (sic!) przechowującej zmienną bool,
tablicy czterech klas Schowek przechowujących zmienne typu float.
#include <iostream.h>
template <class T>
class Schowek {
T zawartosc;
public:
Schowek(T zaw) {zawartosc = zaw;}
T pobierz() {return zawartosc;}
};
void main(int argc, char* argv[])
{
Schowek<int> s1(10);
Schowek<char> s2('s');
Schowek<int*> s3;
Schowek<float[5]> s4;
Schowek<Schowek<bool>> s5;
Schowek<Schowek<float>[4]> s6;
}
Implementacja szablonu klasy Stos do przechowywania dowolnych wektorów danych.
#include <vector>
#include <stdexcept>
template <typename T>
class Stos {
private:
std::vector<T> elem: //elementy
public:
void wstaw(T const&); //wstawia element na szczyt stosu
void zdejm(); //zdejmuje element za szczytu stosu
T zwroc(); //zwraca szczytowy element stosu
Bool pusty(){ //czy stos jest pusty?
return elem.empty();
}
};
template <typename T>
void Stos<T>::wstaw (T const& el)
{
elem.push_back(el); //dołącz kopię przekazanego elementu
}
template <typename T>
void Stos<T>::zdejm()
{
if (elem.empty()) {
throw std::out_of_range(“Stos<>::zdejm(): stos jest pusty”);
}
elem.pop_back() //usuń ostatni element
}
template <typename T>
T Stos<T>::zwroc()
{
if (elem.empty()) {
throw std::out_of_range(“Stos<>::zwroc(): stos jest pusty”);
}
return elem.back() //zwróć kopię ostatniego elementu
}
Do poprzedniego ćwiczenia podaj sposoby użycia szablonu klasy Stos.
#include <iostream>
#include <string>
#include <cstdlib>
#include “stos.hpp”
int main()
{
try {
Stos<int> intStos; //stos elementów typu int
Stos<std::string> stringStos; //stos elem typu std::string
//manipuluj stosem elementów typu int
intStos.wstaw(7);
std::cout << intStos.zwroc() << std::endl;
// manipuluj stosem elemetów typu std::string
stringStos.wstaw(„Ahoj”);
std::cout<<stringStack.zwroc() << std::endl;
stringStack.zdejm();
stringStack.zdejm();
}
catch (std::exception const& ex) {
std:cerr << “Wyjątek: “ << ex.what() << std::endl;
return EXIT_FAILURE; //opuść program zwracając kod błędu
}
}
Literatura :
Zofia Kruczkiewicz (Politechnika Wrocławska) - wykład 13
„C++. Szablony. Vademecum profesjonalisty” Dawid Vandervoorde, Nicolai M. Josuttis,
„Język C++ bardziej efektywny”, Wydawnictwa Naukowo-Techniczne, 1998
Grębosz „Symfonia C++” tom III
3