Clase template
Clasele şi
funcţiile template sunt şabloane pe baza cărora se pot genera
clase şi funcţii folosind un mecanism de expandare
asemănător cu macrodefiniţiile cu parametri. Spre deosebire de
macrodefiniţii, template-urile oferă mecanisme de înlocuire doar
pentru tipuri de date şi constante, însă au avantajul unei
verificări mai bune de către compilator.
Sintaxa utilizată pentru definirea şabloanelor de funcţii este:
template <class T1, class T2, …,
class Tn, tip1 c1, …, tipm cm>
prototip_functie
Exemplu
de construire şi utilizre a unei funcţii template pentru calculul
sumei elementelor unui vector:
#include <iostream>
using namespace
std;
// sablon cu parametri pentru tipul
// elementelor (T) si dimensiunea vectorului
// (
// elemenetelor unui vector
template <class
T, int n>
T suma(T vector[])
{
T suma = 0;
for (int i = 0; i
< n; i++)
suma =
suma + vector[i];
return suma;
}
void main()
{
// declaram si initializam doi vectori de
// tipuri si dimensiuni diferite
int v1[] = {1, 5, 7, 9};
double v2[] = {1.3, 5.7, 7.9, 9.11, 11.9};
// folosim functia template suma pentru a genera
// functii de calcul suma pentru cei doi vectori
cout <<
"Suma 1 este: " << suma<int,
4>(v1) << endl;
cout <<
"Suma 2 este: " << suma<double,
5>(v2) << endl;
}
Funcţia
poate fi folosită pentru orice tip de date care suportă
operaţiile utilizate în cadrul funcţiei: iniţializare
cu 0 şi operatorul +. De exemplu,
putem construi o clasă Complex şi apela funcţia pentru clasa
construită de noi:
#include <iostream>
using namespace
std;
class Complex
{
public:
// constructor
cu valori implicite care
// suporta o
initializare de forma
// Complex c =
0;
Complex(double
r = 0, double i = 0)
: real(r), imaginar(i) {}
// operatorul +
pentru adunarea a doua numere complexe
const
Complex operator + (const
Complex c)
{
real += c.real;
imaginar += c.imaginar;
return
(*this);
}
double
real, imaginar;
};
//
operator pentru afisare
ostream&
operator << (ostream& out, const Complex& c)
{
out << "(" <<
c.real << "," << c.imaginar << ")";
return
out;
}
// sablon cu parametri pentru tipul
// elementelor (T) si dimensiunea vectorului
// (
// elemenetelor unui vector
template <class
T, int n>
T suma(T vector[])
{
T suma = 0;
for (int i = 0; i
< n; i++)
suma =
suma + vector[i];
return suma;
}
void main()
{
// declaram si initializam doi vectori de
// tipuri si dimensiuni diferite
int v1[] = {1, 5, 7, 9};
double v2[] = {1.3, 5.7, 7.9, 9.11, 11.9};
// folosim functia template suma pentru a genera
// functii de calcul suma pentru cei doi vectori
cout <<
"Suma 1 este: " << suma<int,
4>(v1) << endl;
cout <<
"Suma 2 este: " << suma<double,
5>(v2) << endl;
Complex v3[] = { Complex(3.2, 4.2), Complex(1.1, 2.2),
Complex(5, 1) };
cout << "Suma 3 este: "
<< suma<Complex,3>(v3) << endl;
}
Mecanismul
se poate folosi asemănător şi pentru construirea
şabloanelor de clase. Sintaxa este:
template <class T1, class T2, …,
class Tn, tip1 c1, …, tipm cm>
class nume_clasă {…}
Metodele
implementate inline în cadrul clasei folosesc sintaxa obişnuită. În
cazul în care se doreşte implementarea metodelor în afara clasei se va
folosi sintaxa:
template <class T1, class T2, …,
class Tn, tip1 c1, …, tipm cm>
tip_returnat nume_clasă< T1,
T2, …, Tn, c1, …, cm >::
nume_functie(param){…}
Pentru exemplificare vom
construi o clasă vector care să poată stoca orice tip de date:
#include <iostream>
using namespace
std;
// clasa template Vector cu parametrii:
// T -
tipul de date stocat
// n -
template <class
T, int n>
class Vector
{
public:
// constructor
Vector()
{
// alocam spatiu pentru vector
vector = new T[n];
}
// functie inline pentru aflarea dimensiunii
int GetDim() { return
n; }
// operator pentru accesarea elementelor
// definit in afara clasei
T& operator[] (int i);
// destructor pentru dealocarea memoriei
~Vector()
{
delete [] vector;
}
private:
// clasa nu va permite atribuirea
// si constructia prin copiere
Vector operator=(Vector&);
Vector(const Vector&);
// elementele vectorului (de tip T)
T *vector;
};
// definirea in exterior a functiei operator[]
template<class
T, int n>
T& Vector<T,n>::operator[]
(int i)
{
return vector[i];
}
// numarul maxim de caractere pentru un nume
const int
DIM_MAX_NUME = 200;
// clasa persoana pentru testarea vectorului
class Persoana
{
public:
// constructor
Persoana(int cod = 0, char*
nume = "Anonim")
{
// copiem datele primite ca parametri
_cod =
cod;
strcpy(_nume,
nume);
}
// functii de acces
int GetCod()
const { return
_cod; }
const char* GetNume()
const { return
_nume; }
private:
// date membre
int _cod;
char _nume[DIM_MAX_NUME];
};
// operator pentru afisarea unei persoane
ostream& operator
<< (ostream& out, const Persoana&
persoana)
{
out <<
persoana.GetCod() << " " << persoana.GetNume();
return out;
}
void main()
{
int i;
// exemplu de utilizare pentru un vector de intregi
Vector<int, 3> v1; // declarare vector
// initializare elemente
v1[0] = 4; v1[1]
= 47; v1[2] = 2;
// afisare vector
for (i = 0; i < v1.GetDim(); i++)
cout
<< v1[i] << endl;
// exemplu de utilizare pentru un vector de persoane
Vector<Persoana,
2> v2; //
declarare vector
// initializare elemente
v2[0] =
Persoana(1, "Popescu Ion");
v2[1] =
Persoana(2, "Ionescu Maria");
// afisare vector
for (i = 0; i < v2.GetDim(); i++)
cout
<< v2[i] << endl;
}
Se
observă faptul că putem utiliza clasa template Vector pentru a stoca orice tip de date (predefinit sau definit de
utilizator) care suportă operaţiile utilizate în clasă (în
exemplul prezentat doar constructorul implicit pentru: vector = new T[n];).
În exemplele
prezentate şabloanele sunt expandate după acelaşi model
indiferent de tipul de date utilizat. Limbajul oferă posibilitatea de a
preciza modele de expandare specifice pentru anumite tipuri de date. Acest
mecanism se numeşte specializare.
Pentru
exemplificare vom considera următoarea funcţie template pentru
sortarea unui vector:
template <class
T>
void Sortare(T vector[], int n)
{
for (int i = 0; i
< n; i++)
for (int j = i + 1; j
< n; j++)
if (vector[i] > vector[j])
{
// interschimbare elemente
T
temp = vector[i];
vector[i]
= vector[j];
vector[j]
= temp;
}
}
Funcţia astfel
definită va funcţiona corect pentru tipurile de date care
suportă compararea folosind operatorul > şi atribuirea. Dacă
dorim să folosim funcţia pentru sortarea unui vector de şiruri
de caractere vom obţine un rezultat eronat deoarece tipul char*
nu suportă operaţiile necesare. În această situaţie putem
crea o specializare a funcţiei pentru lucrul cu şiruri de caractere.
Specializările pentru funcţii se face prin eliminarea tipului
specializat din listă si utilizarea explicită a acestuia în
prototipul funcţiei:
template <>
void Sortare<char*>(char* vector[], int n)
{
for (int i = 0; i
< n; i++)
for (int j = i + 1; j
< n; j++)
// utilizam functia de comparare pentru stringuri
if (strcmp(vector[i],vector[j]) > 0)
{
// interschimbare elemente
char* temp = new char[strlen(vector[i]) + 1];
strcpy(temp,
vector[i]);
delete [] vector[i];
vector[i]
= new char
[strlen(vector[j]) + 1];
strcpy(vector[i],
vector[j]);
delete [] vector[j];
vector[j]
= new char
[strlen(temp) + 1];
strcpy(vector[j],
temp);
}
}
Exemplu
de utilizare a funcţiei şi a specializării:
void main()
{
// construim si sortam un vector de intregi
int v1[] = {5, 3, 53, 2};
Sortare(v1, 4);
// construim vectorul de siruri
char * v2[3];
v2[0] = new char[8];
strcpy(v2[0],
"popescu");
v2[1] = new char[8];
strcpy(v2[1],
"ionescu");
v2[2] = new char[9];
strcpy(v2[2],
"adamescu");
// si il sortam
Sortare(v2, 3);
}
Se observă
faptul că este posibilă omiterea parametrilor şablonului la
apelul funcţiilor template atunci când compilatorul poate determina
fără echivoc tipul pentru care trebuie făcută expandarea.
Specializarea se
poate similar aplica şi la clase template. În cazul claselor este
posibilă specializarea doar anumitor metode sau a întregii clase.
Să se
construiască clasa template Vector
pentru stocarea unui număr fix de elemente în alocate dinamic. Să se
construiască o specializare pentru tipul bool care să folosească pentru stocare un
vector de întregi fără semn, iar fiecare element va fi stocat pe un
bit.
Rezolvare:
#include<iostream>
#include<cmath>
using namespace std;
// Clasa
template Vector
template<class T>
class Vector
{
public:
// constructor
Vector(int n) : n(n)
{
// alocam memorie pentru
vector
vector = new T[n];
}
// constructor de copiere
Vector(const Vector& v) :
n(v.n)
{
// alocam memorie pentru
vector
vector = new T[n];
// copiem elementele
for (int i = 0; i < n; i++)
vector[i] = v.vector[i];
}
// operatorul de atribuire
const Vector& operator=(const Vector&)
{
// dealocam memoria alocata
delete [] vector;
// alocam memorie pentru noul
vector
vector = new T[n];
// copiem elementele
for (int i = 0; i < n; i++)
vector[i] = v.vector[i];
}
// destructorul
~Vector()
{
// dealocam memoria alocata
delete [] vector;
}
// operatorul pentru citirea
elementelor
T operator[] (int i)
{
return vector[i];
}
// seteaza valoarea unui element din
vector
void Setare(int i, T valoare)
{
vector[i] = valoare;
}
// functie pentru obtinerea
numarului de elemente
int Lungime() { return n; }
private:
int n; // numarul de elemente
T *vector; // vectorul de elemente
};
//
specializarea clasei template Vector
// pentru
stocarea eficienta a vectorilor
// de valori
booleene
template<> // marcajul de clasa template
class Vector<bool> // indicarea
specializarii
{
public:
// constructor
Vector(int n) : n(n)
{
// alocam memorie pentru
vector
vector = new unsigned int[NumarIntregi(n)];
}
// constructor de copiere
Vector(const Vector& v) :
n(v.n)
{
int k =
NumarIntregi(n);
// alocam memorie pentru
vector
vector = new unsigned int[k];
// copiem elementele
for (int i = 0; i < k; i++)
vector[i] = v.vector[i];
}
// operatorul de atribuire
const Vector& operator=(const
Vector&v)
{
// dealocam memoria alocata
delete [] vector;
int k =
NumarIntregi(n);
// alocam memorie pentru noul
vector
vector = new unsigned int[k];
// copiem elementele
for (int i = 0; i < k; i++)
vector[i] = v.vector[i];
}
// destructorul
~Vector()
{
// dealocam memoria alocata
delete [] vector;
}
// operatorul pentru citirea
elementelor
bool operator[]
(int i)
{
int dimElement = sizeof(int)*8;
// obtinem elementul care
contine bitul cautat
unsigned int element = vector[i / dimElement];
// calculam masca elementului
unsigned int masca = 1 << (i % dimElement);
// obtinem bitul cautat din
element
return (bool)(element & masca);
}
// seteaza valoarea unui element din
vector
void Setare(int i, bool valoare)
{
int dimElement = sizeof(int)*8;
// obtinem elementul care
contine bitul cautat
unsigned int& element = vector[i / dimElement];
if (valoare)
{
// trebuie sa setam
bitul pe 1
unsigned int masca = 1;
// mutam bitul pe
pozitia corespunzatoare
masca = masca << (i % dimElement);
// si setam bitul in
vector
element = element | masca;
}
else
{
// trebuie sa setam
bitul pe 0
unsigned int masca = 1;
// mutam bitul pe
pozitia corespunzatoare
masca = masca << (i % dimElement);
// inversam bitii din
masca
masca = masca ^ 0xFFFF; // pp 32 biti
// si resetam bitul in
vector
element = element & masca;
}
}
// functie pentru obtinerea
numarului de elemente
int Lungime() { return n; }
private:
int n; // numarul de elemente
// elementele sunt stocate intr-un
vector
// de intregi (cate 32 pe fiecare
pozitie)
unsigned int *vector;
// calculeaza numarul de intregi
necesari
// pentru stocarea a k elemente
booleene
int NumarIntregi(int k)
{
return (int) ceil((double)k /
(sizeof(int) *
8));
}
};
void main()
{
int i;
Vector<int> v1(3); // declarare
vector de intregi
v1.Setare(0, 7); // setare elemente
v1.Setare(1, 1);
v1.Setare(2, 92);
// afisare elemente vector
for (i = 0; i <
v1.Lungime(); i++)
cout << v1[i] << endl;
Vector<bool> v2(4); // declarare
vector de bool (foloseste specializarea)
v2.Setare(0, false); // setare elemente
v2.Setare(1, true);
v2.Setare(2, true);
v2.Setare(3, false);
// afisare elemente vector
for (i = 0; i <
v2.Lungime(); i++)
cout << boolalpha << v2[i] << endl;
}