Sunteți pe pagina 1din 9

Funcţii şi clase generice (parametrizate).

1. Polimorfism parametric.
2. Polimorfism prin supraîncărcarea funcţiilor (polimorfism ad-hoc).
3. Funcţii polimorfice prin conversie de tipuri.
4. Funcţii generice.
5. Clase generice.

1. Polimorfism parametric.
O entitate polimorfică poate avea mai multe tipuri (polimorfism = mai multe
forme).
Polimorfismul parametric se referă la posibiltatea apelării unei funcţii cu un
număr variabil de parametri. Un exemplu tipic în acest sens îl reprezintă funcţiile de
intrare-ieşire: printf, fprintf, scanf, fscanf, etc.
Polimorfismul parametric este posibil în C şi C++, deoarece la apelul
funcţiilor parametrii sunt puşi în stivă de către programul apelant.
În C, funcţiile definite fără nici un parametru (nici măcar void) nu sunt
verificate de către compilator, aşa că pot fi apelate cu oricâţi parametri, eventual de
tipuri diferite.
În C++, pentru a specifica un număr variabil de parametric vom defini în mod
explicit o funcţie cu număr variabil de parametric, prin tipul (. . .), specificat
întotdeauna ca ultim parametru. Parametrii puşi în stivă de către programul apelant
vor trebui prelucraţi în mod explicit de către programator. În acest scop se utilizează
macroinstrucţiunile va_start, va_arg şi va_end definite în fişierul
<stdarg.h>.
O funcţie cu număr variabil de parametri va avea prototipul:
tip nume (listă_fixă_parametri, . . .);
Lista fixă de parametri trebuie să fie nevidă, deci numărul de parametri va
fi mai mare sau egal cu numărul de parametri ficşi.
Parametrii care sunt în număr variabil sunt convertiţi implicit ca tip, şi
anume:
 toţi întregii la int
 toţi realii la double
Fişierul antet <stdarg.h> conţine definiţii pentru tipul va_list.
Argumentele variabile vor fi accesate printr-o variabilă pointer pa, declarată astfel:
va_list pa;
Iniţializarea pointerului de acces la argumentele variabile - pa se face folosind
macroinstrucţiunea va_start() , indicând adresa ultimului parametru fix
lastarg:
va_start(pa, lastarg);
Pentru parcurgerea listei de argumente variabile se va folosi
macroinstrucţiunea va_arg(), care actualizează pointerul de acces la argumente
pa, pentru a indica următorul argument int sau double, şi întoarce ca rezultat
argumentul curent din lista de parametri variabili:

1
vint=va_arg(pa, int);
sau
vreal=va_arg(pa, double);
Oprirea procesului repetitiv se face folosind informaţiile despre parametrii
ficşi (în vârful stivei se va afla pointerul la format). După ultimul parametru variabil
extras se apelează macroinstrucţiunea:
va_end(pa);
Exemplul: Scrieţi o funcţie care afişează un număr variabil de şiruri de
caractere (cel mult max).
#include <stdio.h>
#include <stdarg.h>
int printvar(int max, …);
void main(void) {
printvar(3,”Ion”,”Vasile”,”Mihai”);
printf(“\n”);
printvar(5,”marti”,”joi”,”luni”,”vineri”,”duminica”);
printf(“\n”);
}
void printvar(int max,…)
{ va_list pa;
int narg=0;
char *siruri[10];
va_start(pa,max);
while(narg < max) {
siruri[narg]=va_arg(pa, char*);
printf(“%s \n”, siruri[narg++]);
}
va_end(pa);
}
Extragerea argumentelor variabile, poate fi făcută, şi cu alte funcţii, în loc de
va_arg(). În acest scop se folosesc funcţiile: vprintf(), vfprintf() şi
vsprintf(). Acestea au prototipurile:
int vprintf(char * format, va_list pa);
 afişează, sub controlul formatului, la ieşirea standard, un număr variabil de
argumente, accesate prin pointerul pa
 întoarce numărul de octeţi afişaţi (rezultat negativ la eroare)
Exemplu:
#include <stdio.h>
#include <stdarg.h>
int printvar(char* fmt, …);
void main(void) {
fmt1[]=”%s %s %s\n”;
printvar(fmt1,”Ion”,”Vasile”,”Mihai”);
}
void printvar(char* fmt,…) {
va_list pa;

2
va_start(pa,fmt);
vprintf(fmt,pa);
va_end(pa);
}

int vfprintf(FILE * fis, char * format, va_list pa);


 afişează, sub controlul formatului, în fişierul fis, un număr variabil de
argumente, accesate prin pointerul pa
 întoarce numărul de octeţi afişaţi (rezultat negativ la eroare)
Exemplu:
#include <stdio.h>
#include <stdarg.h>
#define NUMEFIS “fis.dat”
void printvar(FILE* f, char* fmt, …);
void main(void) {
FILE* f1;
fmt1[]=”%s %s %s\n”;
f1=fopen(NUMEFIS, “w”);
printvar(f1,fmt1,”Ion”,”Vasile”,”Mihai”);
fclose(f1);
}
void printvar(FILE* f, char* fmt,…) {
va_list pa;
va_start(pa,fmt);
vfprintf(f, fmt, pa);
va_end(pa);
}

int vsprintf(char * sir, char * format, va_list pa);


 afişează, sub controlul formatului, în şirul de caractere sir, un număr variabil de
argumente, accesate prin pointerul pa
Exemplu:
#include <stdio.h>
#include <stdarg.h>
int printvar(char* s, char* fmt, …);
void main(void) {
fmt1[]=”%s %s %s\n”;
char s[100];
printvar(s, fmt1, ”Ion”,”Vasile”,”Mihai”);
printf(“%s”,s);
}
void printvar(char* s, char* fmt,…) {
va_list pa;
va_start(pa,fmt);
vsprintf(s,fmt,pa);
va_end(pa);

3
}

Exemplul : Scrieţi o funcţie cu număr variabil de parametri, care simulează


funcţia printf(), acceptând parametri variabili de tip int, double sau
şir de caractere.

#include <stdio.h>
#include <stdarg.h>
void printvar(char* fmt,…) {
va_list pa;
char *p, *psir;
int i;
double d;
va_start(pa,fmt);
for (p=fmt; *p; p++) {
if(*p!=”%”){
putchar(*p);
continue;
}
switch(*++p) {
case ‘d’:
i=va_arg(pa, int);
printf(“%d”,i);
break;
case ‘f’:
d=va_arg(pa, double);
printf(“%lf”,d);
break;
case ‘s’:
for (psir=va_arg(pa,char*);*psir;psir++)
putchar(*psir);
break;
default:
putchar(*p);
break;
}
}
va_end(pa);
}

2. Polimorfism prin supraîncărcarea funcţiilor (polimorfism ad/hoc).


Dacă se consideră funcţia ca un tip, supraîncărcarea funcţiilor reprezintă o
formă de polimorfism numit polimorfism ad-hoc; funcţiile supraîncărcate au acelaşi
nume dar semnături diferite şi cod diferit. Selecţia funcţiei se face după parametrii din
apel.
De exemplu declararea mai multor versiuni ale funcţiei sqrt():
double sqrt(double);
long sqrt(long);

4
3. Funcţii polimorfice prin conversie de tipuri.
Tot polimorfism ad-hoc reprezintă conversia implicită între tipul pointer
generic (pointer la void) şi pointer cu tip.
O funcţie polimorfică, în această accepţiune, va avea ca parametri pointeri
generici . În apelul funcţiei, aceştia vor fi înlocuiţi prin pointeri la tipuri definite.
Exemplificăm prin definirea unei funcţii polimorfice de căutare binară.
Pointerii generici nu pot fi dereferenţiaţi, motiv pentru care îă convertim în
pointeri la octet.
typedef unsigned char OCTET;
typedef int (*PFCP)(void* ch, void* el);
void* CautBin(void* ch, void* tb,
int n, int dim, PFCP fcp){
int rezcp;
OCTET *min = (OCTET*) tb;
OCTET *med;
OCTET *max = (OCTET*) tb + (n-1)*dim;
while(min <= max){
med = min + (max-min)/dim/2*dim;
rezc = (*fcp)(ch, med);
if(rezcp < 0)
max = med – dim;
else
if(rezcp > 0)
min = med + dim;
else
return med;
};
return NULL;
}
Pointerii la elementele de acelaşi tip pot fi comparaţi (min <= max).
Pointerul med se obţine pe o cale mai ocolită: se adună la pointerul min o valoare
constantă – distanţa între min şi max, exprimată în octeţi (max-min) este convertită
mai întâi în indice, prin împărţirea cu dimensiunea elementului dim, este apoi
împărţită la 2, pentru a obţine mijlocul şi transformată din nou în octeţi.
Funcţia de comparaţie are ca parametrii pointeri generici la valorile comparate
şi întoarce o valoare negativă, 0 sau o valoare pozitivă, în funcţie de rezultatul
comparaţiei. Ea va fi adresată printr-un pointer (pointer la funcţie) .
Explicitarea funcţiei concrete de comparaţie se va face în apel. Vom considera
două cazuri: compararea a două valori întregi, respectiv compararea a două şiruri de
caractere. Pointerii generici sunt convertiţi în pointeri la tipul respectiv (întreg sau şir
de caractere), se face dereferenţierea şi se returnează ca rezultat diferenţa valorilor,
respectiv rezultatul comparaţiei şirurilor de caractere.
int cpint(void* ch, void* el){
return *((int*)ch) - ((int*)el);
};

5
int cpsir(void* ch, void* el){
return strcmp((char*) ch, *((char**)el));
};
4. Funcţii generice.
Există multe funcţii (şi clase) înrudite între ele, cu excepţia unor tipuri. De
exemplu o funcţie care sortează un tablou de întregi va diferi foarte puţin de un
algoritm de sortare a unui tablou de reali.
Mecanismul şabloanelor (templates) crează funcţii sau clase generice
(parametrizate) utilizând tipurile ca parametri. Un şablon defineşte o familie de
funcţii, respectiv de clase.Aceste tipuri se specifică la definire, în mod generic,
urmând apoi instanţierea tipurilor generice cu tipuri concrete.
Mecanismul reutilizează codul sursă, prin expandare, în condiţiile verificării
complete a tipurilor.
O entitate template descrie un şablon pentru un număr nespecificat de entităţi
concrete, înrudite între ele.
De exemplu, pentru calculul diferenţei în valoare absolută a două valori
întregi, reale sau întregi lungi s-ar putea scrie o varietate de funcţii supraîncărcate,
care ar avea acelaşi cod, diferind doar semnătura (tipul parametrilor, nu şi numărul
lor).
int max(int x, int y){
return x – y > 0? x : y;
}
long max(long x, long y){
// identic
}
float max(float x, float y){
// identic
}
Toate variantele ar putea fi comasate într-una singură, având tipul
parametrizat:
template <class T>
T max(T x, T y){
return x - y > 0? x : y;
}
Definirea unei funcţii generice cuprinde:
template <par1, par2,..., parn>
declaraţie funcţie parametrizată;
Parametrii din lista de parametri a şablonului pot fi tipuri de date, caz în care
se specifică sub forma class Ti sau constante.
Parametrii din lista parametrilor şablonului (template) trebuie să apară toţi în
lista de parametri formali a funcţiei generice.
Instanţierea funcţiei parametrizate se face prin:

6
nume_funcţie_parametrizata(expr1, expr2,...,exprn)
unde expr1, expr2,...,exprn sunt expresii din care se deduc tipurile
concrete.
Tipul generic T poate fi instanţiat cu orice tip concret, inclusiv un tip definit de
utilizator, care suportă operatorii - şi > şi care poate fi întors de către funcţie.
Compilatorul suportă funcţia generică T max(T, T) prin generare de cod
sursă pentru o funcţie C max(C, C), unde C este tipul concret al parametrilor de
apel.
Generarea de cod pentru entitatea template are loc la compilare.
Parametrii şablonului pot fi tipuri predefinite ,tipuri definite de utilizator
(clase) sau constante.
La apelul funcţiei parametrizate, tipul argumentului determină care versiune a
şablonului este folosită. De exemplu max(10, 25) selectează funcţia int
max(int, int). Compilatorul deduce tipul argumentului din apel, dacă reuşeşte
să le identifice unic, fără a efectua vreo conversie implicită. Astfel apelul max(10,
2.5) este ambiguu deoarece ar putea fi interpretat ca int max(int, int)sau
float max(float,float)
Situaţiile de acest fel trebuiesc clarificate prin supraîncărcare:
float max(int x, float y){ return x-y>0? x : y; };
float max(float x, int y){ return x-y>0? x : y; };
Redefinirea funcţiei are prioritate asupra definiţiei şablonului. Astfel având:
template <class T>
T min(T a, T b){
return (a < b)? a : b;
}
şi
char* min(char* s1, char* s2){
return (strcmp(s1,s2) < 0? S1 : s2);
}
pentru argumente şiruri de caractere va fi aleasă varianta supraîncărcată a funcţiei,
fără a se încerca potrivirea cu şablonul.
Funcţiile membre ale unei clase generice, sunt, la rândul lor funcţii generice,
adică pot fi aplicate unor tipuri de date diferite.
În afara acestora se pot defini şi funcţii nemembre generice reprezentând
familii de funcţii.
Fie funcţia generică:
template <class T>
T minim(const T* x, int n){
T xm = x[0];
for(int i=1; i<n; i++)
if(x[i]<xm)
xm = x[i];

7
return xm;
};
Instanţierea şablonului,, adică înlocuirea parametrilor şablonului cu parametrii
efectivi are forma:
int ix[] = { 3, 8, 1, 5, 13, 7};
float fx[] = {1.5, -3.2, 7.3, 4.8};
void main(){
int n = sizeof(ix) / sizeof(int);
int imin = minim(ix, n);
n = sizeof(fx) / sizeof(float);
float fmin = minim(fx, n);
. . .
}
Toate funcţiile generate pornind de la o funcţie generică sunt versiuni
supraîncărcate ale uneia dintre ele. Mecanismul funcţiilor generice poate fi deci privit
ca un mijloc de realizare a polimorfismului ad-hoc.
5. Clase generice.
Clasele colecţii conţin obiecte de un tip particular (de exemplu o listă
înlănţuită de întregi sau un tablou de structuri. Se pot defini familii de clase colecţii,
membrii acestora diferind numai prin tipul elementelor.
Considerăm clasa tablou de întregi: pentru a folosi un tablou de reali, de
complecşi sau de şiruri de caractere s-ar putea copia implementarea clasei, modificând
tipul datelor şi numele clasei. Astfel am avea clasele intArray, StringArray,
ComplexArray, etc.
Familia de clase poate fi reprezentată printr-o clasă generică (parametrizată).
Aceasta specifică modul în care pot fi construite clasele individuale, care se deosebesc
numai prin tipul elementelor pe care le conţin.
Instanţierea unui şablon reprezintă generarea declarării unei clase dintr-o
clasă parametrizată şi un argument generic.
O versiune a şablonului pentru un argument particular poartă numele de
specializare.
Utilizarea unei clase generice presupune generarea de către compilator a
fiecărei clase individuale, corespunzător tipurilor care instanţiază clasa generică.
O clasă generică se reprezintă astfel:
template <listă_argumente_generice> declarare_clasă;
Instanţierea unei clase generice se face prin:
nume_clasă <listă_argumente_concrete> nume_obiect;
Definim clasa generică (parametrizată):
template <class T>
class Array{
public:
Array(int d=10): a(new T[dim]), dim(d){}
~Array(){delete a;}

8
private:
T *a;
int d;
};
Instanţierea clasei şablon se face prin:
Array<int> x;
Array<Complex> c;
Array<String> s;
Definirea unei clase şablon se face prin:
template <lista_parametri_sablon>
declaratie sau definitie clasa;
Fiecare parametru din şablon reprezintă un tip:
a. definit de utilizator: class T
b. predefinit: int, double, etc
Exemplu
template <class T, int d> class Buffer{
T a[d];
int dim;
public:
Buffer() : dim(d){}
};
şi instanţieri de forma:
Buffer<char, 63> bufc;
Buffer<Rec, 10> bufr;
cu:
class Rec{ char s[10]; };
O funcţie generică de căutare în bufferul generic a unui şir va avea semnătura:
template <class T, int n>
T& cautare(Buffer<T,n> &b, const char* p);
Funcţiile generice pot fi supraîncărcate, cu condiţia ca instanţele să aibă
semnături diferite. De exemplu:
template <class T>
class Array{. . .}; //clasa generica
template <class T>
T suma(Array<T>, int); //functie generica
template <class T>
T suma(T*, int); //functie generica supraincarcata
void main(){
Array<int> A[100]; //instantiere clasa generica
int a[100];
int s1 = suma(A, 100);//instantiere functie generica
int s2 = suma(a, 100);
}

S-ar putea să vă placă și