Sunteți pe pagina 1din 729

PROGRAMAREA CALCULATOARELOR 2

MODUL 1

CURS 1
CUPRINS
1. RECURSIVITATE
1. Noţiuni introductive
2. Funcţii recursive. Încărcarea stivei
3. Ieşirea din recursivitate

2. METODE DE PROGRAMARE RECURSIVE SI


NERECURSIVE
• Metoda backtracking
• Metoda divide et impera

3. TEHNICI DE CĂUTARE ŞI SORTARE


• Metode de căutare
 Căutarea binară
• Metode de sortare
 Metode simple de sortare
 Metode avansate de sortare 2
 Funcţii de bibliotecă de cautare si sortare
1. RECURSIVITATE

1.1. Noţiuni introductive

 Recursivitatea în matematică

 Recursivitatea în programare

 Algoritm recursiv:
Pn
Pn-1

Pk → val
3
Exemple de algoritmi implementați recursiv

 Factorialul:
 n!= 1*2*3*...*n, definiţie nerecursivă
 n! = n * (n-1)!, definiţie recursivă, știind că 0! =1

 Șirul lui Fibonacci:


 fn = 0, dacă n=0
 fn = 1, dacă n=1
 fn = fn-1 + fn-2, dacă n>= 2

4
 cmmdc(m, n) =
 m, dacă n=0
 n, dacă m=0
 cmmdc(n, m%n), dacă m>n
 cmmdc(m,n%m), dacă n>m

 cmmdc(m, n) =
 m, dacă m=n
 cmmdc(m-n, n), dacă m>n
 cmmdc(m, n-m), dacă m<n

5
Aranjamente de n luate câte k
 A(n,k)=n!/ (n-k)!=n*(n-1)*…(n-k+1)=n*[(n-1)*…*((n-
1)-(k-1)+1)]= n*A(n-1, k-1)
 A(n,1)=n; // condiţia de iesire

Combinări de n luate câte k


 C(n,k)=n!/ (k!*(n-k)!)=[n/ (n-k)]* C(n-1,k)

 C(n,k)=n, dacă k=1


 C(n,k)=1 dacă k=0 sau n=k
C(n,k) = C(n-1, k) + C(n-1, k-1)

6
FUNCŢIA LUI ACKERMANN :

Pentru valori mici ale lui m (1, 2, 3) valoarea creşte relativ


încet (aproape exponenţial cu n). Pentru m>=4 valoarea sa
creşte foarte rapid. Exemplu: A(4,2) este un întreg cu 19,729
cifre (aprox 2×1019728)
1.2. Funcţii recursive

 Recursivitatea în programare – subprograme


 În limbajul C/C++ - funcţii

 Funcţie recursivă → autoapel

 Autoapel:
 direct (recursivitate directă)
 indirect (recursivitate indirectă)

8
 Factorialul n! = n * (n-1)!

int factorial(int n){


if (n == 0)
return 1;
else
return n*factorial(n-1); //adresa 2
}

int main(void)
{
cout <<" Factorial de 3 = " << factorial(3) << endl;
cout<<"adresa 1";
}
9
Câteva observații !!!

 La apelul recursiv al unei funcţii se creează o nouă


instanţă de calcul pentru acea funcţie.

 La fiecare apel - pe stiva locală funcţiei se pun un nou


set de variabile locale (inclusiv parametrii formali), cu
aceleași nume, dar diferite ca zonă de memorie și cu
valori diferite la fiecare apel

 în fiecare moment e accesibilă doar variabila din vârful


stivei

10
main()

int factorial(int n) factorial (3)


{
if (n == 0)
return 1;
else
return n*factorial(n-1); factorial (2)
}

factorial (1)

factorial (0)
11
 Laapelul unei funcţii, precum şi la autoapelul ei,
pe stivă se vor depune:

 valorile parametrilor transmişi prin valoare


 adresele parametrilor transmişi prin adresă
(referinţă, pointeri)
 valorile tuturor variabilelor locale nestatice
 adresa de revenire la instrucţiunea imediat
următoare apelului

12
EVOLUŢIA STIVEI factorial(0)

Stiva program
factorial(1) adresa2
Stiva program 0
factorial(2)
adresa2
adresa2
Stiva program
1
1
factorial(3)
adresa2 adresa2
adresa2
Stiva program
2 2 2
adresa1 adresa1 adresa1
adresa1
3 3 3
3

13
 Şirul lui Fibonacci
 f(n) = 0, dacă n=0
 f(n) = 1, dacă n=1
 f(n) = f(n-1) + f(n-2), dacă n>= 2

int fib(int n)
{
if (n < 2)
return n;
else
return fib(n-1)+fib(n-2);
}

int main(void){
cout << "Fibonacci de 4 = " << fib(4) << endl;
}
14
15
 Metoda este însă foarte ineficientă, timpul de calcul fiind
de ordinul Φ (numit secţiunea de aur), ce reprezintă un
timp exponenţial,
 Ineficienţa este dată de faptul că se evaluează unele
elemente ale şirului de mai multe ori:

16
Observații!

 Orice algoritm recursiv poate fi transformat într-unul


iterativ și invers

 Orice număr natural poate fi descompus într-o sumă


de termeni neconsecutivi ai șirului lui Fibonacci
(teorema lui Zeckendorf).

 Codificarea Fibonacci – orice număr natural n poate


fi reprezentat printr-o secvență de biți care se
termină cu “11”, dar în rest nu apar 2 biți consecutivi
cu valoarea 1

17
 Primele n numere din şirul lui Fibonacci se pot
calcula iterativ folosind un şir ajutător :

void fib(int n, int *p)


{
int l=2;
p[0]=0; p[1]=1;
for(; l < n; l++)
p[l] = p[l-1] + p[l-2];
}

 Acest algoritm rezolvă aceeaşi problemă, însă într-


un timp liniar şi nu exponenţial
18
Numerele din şirul lui Fibonacci se pot calcula şi fără şirul ajutător:
//Solutia iterativa pentru generarea sirului lui Fibonacci pana la N dorit

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
int l = 1, j = 0, k, N;
printf("Numarul de elemente dorit este: ");
scanf("%d", &N);
printf("f[0]= 0\nf[1]= 1\n");
for (k = 2; k < N; k++) {
l = l + j; // elem. de pe poz k din sir
j = l - j; // se pregateste elementul de pe poz k-1
printf("f[%d]= %d\n", k, l);
}//end for 19

}
 Combinările
C(n,k) = C(n-1,k) + C(n-1,k-1)

int comb(int n, int k)


{
if(k==0) || (n==k) return 1;
if(k==1) return n;
return (comb(n-1, k) + comb(n-1, k-1));
}

Şi în acest caz se fac evaluări multiple:


C(5,3) = C(4,3) + C(4,2)
 C(4,3) = C(3,3) + C(3,2)
C(3,2) = C(2,2) + C(2,1)
 C(4,2) = C(3,2) + C(3,1)
C(3,2) = C(2,2) + C(2,1)
20
 c.m.m.d.c.

int cmmdc(int a, int b)


{
if(a==0) return b;
if(b==0) return a;
if(b>a) return cmmdc(a, b%a);
return cmmdc(b,a%b);
}

21
 Concluzii:
 Un algoritm recursiv presupune mai multe niveluri

 Pe fiecare nivel se întâmplă acelaşi lucru, dar la o


dimensiune mai mică a problemei

 Există cel puţin un nivel ce admite o rezolvare


directă (fără proces recursiv)

 D.p.d.v. al limbajului de programare apare un


autoapel

22
1.3. Ieşirea din recursivitate

 Situații care trebuie evitate în cazul recursivității:


- încărcarea excesivă a stivei → stack overflow
- autoapelarea la infinit → time out

Soluția: autoapelul să fie legat de îndeplinirea unei condiţii,


care să asigure oprirea din acel ciclu de autoapeluri + un
număr redus de parametri pentru funcția recursivă

23
 Posibilităţi:
 se asociază funcţiei recursive un parametru întreg n
şi apelul se face cu valorile (n-1), (n-2),… cât timp
n>0 și nu e indeplinită conditia de iesire
 se asociază funcţiei recursive un parametru logic, iar
apelul se face atâta timp cât parametrul are valoarea
TRUE

 Utilizarea tehnicilor recursive nu conduce, de obicei, la


soluţii optime:
 analiza eficienţei soluţiei iterative (nerecursive) şi a
celei recursive, alegându-se soluţia cea mai
avantajoasă

 Soluţia recursivă duce însă la soluţii mai elegante şi mai


uşor de urmărit
24
 Ce trebuie luat în calcul la alegerea rezolvărilor cu
ajutorul metodelor recursive?
 necesarul de memorie
 timpul de execuţie
 parametrii transmiși (pentru a nu încărca stiva se
preferă să fie folosiți parametrii globali acolo unde e
posibil)

 Când se preferă varianta recursivă?


- când înlocuirea recursivităţii cu iteraţia cere un efort
deosebit, algoritmul pierzându-şi din claritate
- testarea şi întreţinerea devin dificile

25
 Exemplul 1: suma cifrelor unui număr întreg
 Abordare:
 Recursivă: suma cifrelor numărului din stânga +
ultima cifră
 Nerecursivă (iterativă): suma cifrelor (cifră cu cifră)

int suma_cifre_recursiv(int n)
{
if (n==0)
return 0;
else
return suma_cifre_recursiv(n/10)+n%10;
}

26
Exemplu:
suma_cifre_recursiv(1234);
 Încărcarea stivei

Adresa 2
n=0
Adresa 2
n=1
Adresa 2
n=12
Adresa 2
n=123
Adresa 1
n=1234 27
 Descărcarea stivei

Adresa 2
suma_cifre_recursiv(0) n=0
0
Adresa 2
suma_cifre_recursiv(1) n=1
0+1%10=1
Adresa 2
suma_cifre_recursiv(12) n=12
1+12%10=3
Adresa 2
suma_cifre_recursiv(123) n=123
3+123%10=6
Adresa 1
suma_cifre_recursiv(1234) n=1234
6+1234%10=10
28
main()
VARIANTA ITERATIVĂ
int suma_cifre_iterativ(int n)
{
int s=0;
while(n != 0) {
s += n%10;
n /= 10;
}
return s;
}

29
 Exemplul 2: suma elementelor unui tablou
unidimensional de tip numeric

 Abordare:
 Recursivă: suma primelor (n-1) elemente + ultimul element
 Nerecursivă: suma, element cu element

int suma_elem_recursiv(int tab[ ], int n)


{
if (n==1)
return tab[0];
else
return suma_elem_recursiv(tab, n-1)+ tab[n-1];
}

30
VARIANTA NERECURSIVĂ

int suma_elem_iterativ(int tab[ ], int n)


{
int s=0;
for(int i=0; i<n; i++)
s += tab[i];
return s;
}

31
 Exemplul 3: şir palindrom
 Un şir este palindrom dacă:
 primul caracter este identic cu ultimul caracter
 restul şirului este un şir palindrom
 exemple: rotor, madam, cojoc

// functia returneaza 1 pt. palindrom si 0 in caz contrar


int mirror(char *s){
if (strlen(s) <= 1)
return 1;
else
if(s[0] == s[strlen(s) - 1])
return mirror(substr(s,1, strlen(s) - 2);
else
return 0;
} 32
unde funcția substr() este următoarea:

char* substr(char* s,int pi,int pf)


{
int k=0;
char *n=new char[pf-pi+1];
for(int i=pi; i<=pf; i++,k++)
*(n+k)=*(s+i);
*(n+k)=0;
return n;
}

33
 Tipuri de probleme care de obicei se rezolvă cu
ajutorul funcţiilor recursive:

 metode de divizare, divide et impera;


 metode de căutare cu revenire, backtracking;
 metoda Greedy;
 metoda programării dinamice;
 metode în care datele sunt definite recursiv, etc.

34
PROGRAMAREA CALCULATOARELOR 2

Modul 1
Curs 2
CUPRINS
2. Metode de programare recursive/nerecursive
2.1. Metoda backtracking
2.2. Metoda divide et impera

36
2. Metode de programare recursive/nerecursive
2.1. Metoda backtracking

 Există aplicaţii pentru care trebuie determinată


configuraţia unui set de date de intrare ce maximizează
sau minimizează o anumită funcţie

 Astfel de probleme se numesc probleme de optimizare,


iar funcţia respectivă se numeşte funcţie criteriu sau
funcţie obiectiv

37
 O primă modalitate de rezolvare:
- considerarea tuturor combinaţiilor posibile ale
candidaţilor
- evaluarea funcţiei criteriu pentru fiecare combinaţie

 Această metodă (algoritm) se numeşte metoda forţei


brute (brute force):
 cel mai adesea, necesită un timp de calcul
exponenţial
 în multe cazuri numărul total de combinaţii poate
atinge dimensiuni astronomice

38
 Optimizare prin reguli specifice problemei care:
 fie permit selectarea combinaţiilor ce vor fi
evaluate mai întâi
 fie permit eliminarea anumitor combinaţii, fără ca
acestea să mai fie evaluate

 O altă abordare - utilizarea constrângerilor pentru a


reduce numărul de combinaţii pentru care se
evaluează funcţia criteriu
 Metoda backtracking face parte din această
categorie şi este, de fapt, o rafinare a metodei forţei
brute

39
Principiul:

 In metoda backtracking standard, soluţia se poate


reprezenta sub forma unui vector (tablou
unidimensional) X:
X(x1,x2,…, xn) ϵ S=S1xS2x…xSn
unde:
 S este spaţiul soluţiilor posibile, spaţiu care este
finit (produs cartezian a Si )
 xi, i=1,...n, sunt componentele soluţiei S, care pot
să aibă valori în domeniile Si

• Metoda îşi propune să determine toate soluţiile


rezultat posibile
40
• In backtracking se traversează
spaţiul soluţiilor până la găsirea
unei soluţii finale cu revenire
pe “urma” lăsată

• Soluţiile date de backtracking trebuie să satisfacă un set


de constrângeri:
explicite - reguli ce definesc (restrâng) setul de valori
pentru fiecare componentă
implicite - determină care combinaţii din spaţiul soluţiilor
satisfac funcţia criteriu
41
 Caracteristici:

 soluţia finală se construieşte secvenţial;


 se porneşte cu un vector gol şi la fiecare pas se
extinde vectorul parţial cu o nouă valoare pentru
acea componentă;
 pe măsura utilizării unei noi valori la o componentă,
se evaluează funcţia criteriu;
 dacă valoarea adăugată conduce la o soluţie
posibilă, se trece la următoarea componentă

42
CARACTERISTICI:
 dacă valoarea considerată nu conduce la o soluţie
plauzibilă, se alege o altă valoare din alternativele
posibile pentru acea componentă ;

 procesul continuă secvenţial pană se obţine o soluţie


finală.

 aici se continuă căutarea altor valori, iar dacă


alternativele posibile asociate unei componente sunt
epuizate, se revine la componenta anterioară (revenire
pe urmă);

 procesul revenirii continuă, iar dacă în urma revenirii nu


mai sunt componente (e prima componentă a soluţiei),
procesul se încheie.
43
 De obicei, se folosesc funcţii recursive:
 fiecare instanţă se ocupă de o nouă componentă şi
prelucrează toate valorile posibile păstrând numai
acea valoare care este consistentă cu următorul
apel recursiv

 Există şi variante implementate nerecursiv (iterative)

44
 Atât în varianta recursivă cât şi în cea nerecursivă, iniţial
se determină (citesc) date referitoare la:
 numărul componentelor, n;
 domeniul valorilor, h;
 alte date necesare pentru elaborarea algoritmului
(functia criteriu)

 In C/C++ considerăm o soluţie sub forma unui tablou


unidimensional:
x[k], k=0,…,n-1 şi x[k]=1,…,h,

 deci soluţia are n componente 0,...,n-1, fiecare


componentă având valori în domeniul 1,...,h.
45
 Concluzie:
La orice problemă de backtracking stabilim:

1. k = 0,…,n-1, numărul de componente ale soluției;


2. x[k]=1,…,h, domeniul valorilor posibile ale unei
componente;
3. posibil(k), condiţia de continuitate

46
47
Varianta nerecursivă

//initializari pas si componente solutie

k = 0;

x[k] = 0;

// repeta cat timp exista componente


do {
// atata timp cat mai sunt valori de ales pentru o
//componenta
while( x[k] < h )
{
// trec la urmatoarea valoare
48
x[k] = x[k]+1;
//daca solutia partiala este posibila
if (posibil(k)) {
// daca am ajuns la o solutie completa
if ( k==n-1)
afiseaza_solutia(X);
else {
// trec la urmatoarea componenta
k = k+1;
x[k] = 0;
}
}
}//while
k = k-1; // pasul inapoi (componenta anterioara)
} while(! (k<0)); // (k>=0) nu mai exista componente
49
Varianta recursivă
void Backtracking(int k)
{
// pentru toate valorile pe care le poate lua x[k]
for(int i=1; i <= h; i++)
{
x[k] = i;
// daca solutia partiala este posibila
if ( posibil(k)) {
if ( k == n-1) // solutie completa
AfiseazaSolutia(X);
else
Backtracking(k+1); // apel recursiv
}
}
50
 Apel în main( ):
// initializari
Backtracking(0);

 Funcţia posibil( ) dă condiţia de continuitate (condiţie


internă) care stabileşte ce relaţii trebuie respectate
între componentele unei soluţii şi returnează:
 TRUE dacă soluţia parţială este corectă şi se poate
continua;
 FALSE în caz contrar

51
 Există variante de backtracking în care soluţiile nu au
aceeaşi lungime (nr. variabil de componente) sau se
cere obţinerea unor soluţii optimale, etc.

 Exemple:
1. colorarea benzilor unui drapel cu n benzi având h
culori disponibile, benzile alăturate având culori
diferite (număr fix de componente)
2. descompunerea unui număr n, în sumă de
numere naturale (număr variabil de componente)
3. problema comis-voiajorului

52
Problema drapelului – varianta nerecursivă
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define DIM 5

int posibil(int);
void afis_sol(void);
int x[DIM],n;

int main(void){
int k,h;
printf("\nIntrodu numarul de culori ale unui drapel(benzi,
<=DIM=5)\n");
scanf("%d",&n);
printf("Introdu numarul maxim de culori posibile ale unei benzi \n");
scanf("%d",&h);
53
k=0;
x[k]=0;
do {
while(x[k] < h) // mai sunt valori posibile pentru
// componenta k
{
x[k]++; //trec la urmatoarea valoare
if(posibil(k))
if(k==(n-1)) afis_sol(); //e gata solutie
else {k++; x[k]=0; } // trec la urmatoarea
// componenta cu
// valori de la zero
}//while
k--; // nu mai sunt valori pentru componenta k. Revin la
// componenta k-1
}while(!(k<0)); // m-am intors mai mult decat se putea,
// sau k>=0
}//main 54
int posibil(int k)
{
if(k==0)return 1; //initial totul este posibil
if(x[k-1]==x[k]) return 0; // doua culori alaturate
// identice - nu e posibil
return 1; //culorile alaturate nu sunt identice
}//posibil

void afis_sol(void)
{
for(int i=0;i<n;i++) //afisez solutia curenta pentru
// componentele drapelului
printf("%d ",x[i]);
printf("\n");
}//afis_sol
55
PROBLEMA DRAPELULUI - VARIANTA RECURSIVĂ
#include <stdio.h>
#define DIM 5
int posibil(int);
void afis_sol(void);`
void dr_rec(int k);
int x[DIM], n, h;
int main(void){
printf("\nIntrodu numarul de culori ale unui drapel(benzi,
<=DIM=5)\n");
scanf_s("%d",&n);
printf("Introdu numarul maxim de culori posibile ale unei
benzi \n");
scanf_s("%d",&h);
dr_rec(0); //apel functie recursive

}//main 56
void dr_rec(int k)
{
for(int i=1;i<=h;i++)
{ x[k]=i;
if(posibil(k))
if(k==(n-1)) afis_sol(); //solutie finala
else dr_rec(k+1); //trec la urmatoarea
//componenta
}
}//dr_rec

Obs! Funcțiile posibil() și afis_sol() sunt identice cu cele de


la varianta nerecursivă

57
Descompunerea numărului - Varianta iterativă
#include<stdio.h>
#define DIM 20
int posibil(int k, int &s);
void afis_sol(int k);
int x[DIM], n;
int main(void){
int k,sum;
printf("\nNumarul ce va fi descompus( <=DIM=20) : ");
scanf_s("%d",&n);
printf("\n\tSolutii posibile :\n");
// initializari
k = 0;
x[k] = 0;
58
do {
while(x[k] < n)
{
x[k]++;
if(posibil(k, sum)) {
if(sum == n) // solutia finala
afis_sol(k);
else {
k++;
x[k] = 0;
}
}
} // bucla valori componenta
k--; // pas inapoi
} while(!(k < 0)); // bucla componente
59

}
Descompunerea numărului - Varianta recursivă
#include <stdio.h>
#define DIM 20
void back_SubSet(int n);
int posibil(int k, int &s);
void afis_sol(int k);
int x[DIM], n;
int main(void){
printf("\nNumarul ce va fi descompus( <=DIM=20) : ");
scanf_s("%d",&n);
printf("\n\tSolutii posibile :\n");
back_SubSet(0); // apel initial

}
60
void back_SubSet(int k)
{
int sum;
x[k] = 0;
// pentru toate valorile pe care le poate lua x[k]
while(x[k] < n)
{
x[k]++;
if(posibil(k, sum)) // solutie posibila
{
if(sum == n) // solutie completa
afis_sol(k);
else
back_SubSet(k+1);
}
} 61
}
int posibil(int k, int &s)
{
s=0;
if(k==0) return 1; // initial totul este posibil
if(x[k] > x[k-1]) {
for( int i=0; i<=k; i++)
s += x[i];
if(s <= n)
return 1;
}
return 0;
}

void afis_sol(int k){


printf("\n\t");
for(int i=0; i<=k; i++)
printf("%d ", x[i]); 62
}
Problema Comis voiajorului – varianta recursivă
/*n orase intre care pot exista legaturi directe cu un cost dat in
COST[n][n], daca nu, e 0. Pornind din orasul i sa se determine ruta pe
care o foloseste pentru a merge doar o data in cele n orase cu cost
minim si a reveni */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define MAX 7

void gene(int k); //metoda recursiva


void cit(int [][MAX],int &);
void afis(int [][MAX],int &);
int max_cost(int [][MAX],int &);
void afis_sol(long &);
int posibil(int);

int x[MAX],Y[MAX]; // x-componentele solutiei, y-rezultatul


int COST[MAX][MAX];
int n; 63
long cost_M,C;
int main(void){
int k;
printf("Introdu dim matrice costuri(nr.orase) <=7\n");
scanf("%d",&n);
printf("Introdu si afis matricea costurilor C\n");
cit(COST,n);
afis(COST,n);
cost_M=(n+1)*(long)max_cost(COST,n)+1;
// printf("Cost maxim= %ld\n",cost_M);
k=0;
printf("Introdu orasul initial (0 ~ n-1)");
scanf("%d",&x[k]);
gene(1);
afis_sol(cost_M); //e gata solutie

}//main
64
int posibil(int k)
{
if(k==0)return 1;
if(COST[x[k-1]][x[k]]!=0){ //drum direct
for(int i=0;i<k;i++) //orasul nu a mai fost ales
if(x[k]==x[i]) return 0;
return 1;
}
return 0;
}//posibil

65
void gene(int k) {
for(int i=0;i<n;i++){ // valori intre 0 si n-1
x[k]=i;
if(posibil(k))
if((k==(n-1))&& (COST[x[n-1]][x[0]]!=0)){
C=0;// stabilire cost pentru solutia determinata
for(int i=0;i<n-1;i++) C+=COST[x[i]][x[i+1]];
C+=COST[x[n-1]][x[0]];
if(C<cost_M){
for(int i=0;i<n;i++)
Y[i]=x[i];
cost_M=C; //salvare solutie cost minim
}

}
else gene(k+1);
} //for
66
} // end gene
 Concluzii

 Soluţia se determină printr-o căutare sistematică în


spaţiul soluţiilor

 Se construieşte vectorul soluţie (tabloul


unidimensional) componentă cu componentă şi se
testează la fiecare pas dacă vectorul parţial are
şanse de succes:
 dacă da, se continuă
 dacă nu, vectorul parţial este ignorat

67
CONCLUZII:
 Etape specifice metodei backtracking:
 Specificarea vectorului soluţie X[N]:
 dimensiunea N,

 semnificaţia elementelor X[i],

 domeniului valorilor pentru X[i] (constrângeri explicite),

 valoarea de pornire a0,

 constrângerile (implicite) pentru componentele vectorului


(ce satisfac obţinerea soluţiei)
 Se implementează funcţia posibil() ce va verifica respectarea
constrângerilor implicite

 Metoda nu se aplică acolo unde dimensiunea vectorului


soluţie ar putea fi foarte mare, datorită timpului mare de
determinare a soluțiilor
68
 Exemple de probleme ce se pretează la rezolvarea
prin metoda backtracking (laborator) :

 colorarea unui drapel cu n benzi, fiecare bandă


putând avea h culori
 problema damelor de şah: se cere să se plaseze N
dame pe o tablă de şah de dimensiune NxN, fără să
se atace una pe alta;
 problema investirii optime de capital: pentru un
investitor cu un capital C şi n oferte la care trebuie
avansate fondurile fi şi care aduc beneficiile bi, se
cere selectarea acelor oferte care îi aduc beneficiul
maxim;

69
 determinarea petelor de culoare de arie maximă pentru o
imagine reprezentată sub forma unei matrici cu NL linii şi
NC coloane care conţine NG nuanţe de gri;

- traseul comis-voiajorului : stabilirea celui mai scurt traseu,


fiind date o localitate de pornire, n localităţi ce trebuie
vizitate o singură dată, revenirea fiind obligatorie la
localitatea de pornire (comis voiajor, algoritmul de rutare
a pachetelor Bellman-Ford, Dijkstra).

 problema sumei subseturilor: se considera un set P de n


numere pozitive P(p0, p1, ..., pn-1) şi M un număr pozitiv;
să se găsească toate subseturile lui P pentru care suma
numerelor este M;

70
2. METODE DE PROGRAMARE
RECURSIVE / NERECURSIVE

2.2. Metoda divide et impera

 Principiul propus de metodă:


 se descompune problema în subprobleme, în mod
recursiv, până când ajungem la o subproblemă pe
care o putem rezolva direct
 soluţiile subproblemelor se vor combina obţinând
soluţia finală a problemei

71
Căutarea binară
 Fie un şir de elemente ordonat. Se pune problema găsirii
unui element (cheia) în şir.
 Descompunerea problemei :

 se compară cheia cu valoarea din mijloc


 dacă avem egalitate am găsit poziţia în şir
 dacă nu, vom şti în care din cele două jumătăţi de şir
trebuie să căutăm mai departe pornind de la valoarea
comparată
 se continuă în acest mod pînă când găsim elementul în
şir sau până când vom ajunge la un şir vid

72
Valoarea căutată: 23

73
VALOAREA CĂUTATĂ: 88

74
 Funcţia de căutare va returna poziţia în şir dacă s-a găsit
elementul dat sau valoarea (-1) în caz de insucces:

int CautareBinara(int *p, int inc, int sfr, int val) //recursiv
{
int mij;
mij = (inc + sfr)/2;
if(p[mij] == val)
return mij;

if(inc <= sfr) {


if(p[mij] > val)
sfr = mij - 1;
else
inc = mij + 1;
return CautareBinara(p, inc, sfr, val);
}
return -1; 75
}
 Problema turnurilor din Hanoi

 Fie 3 tije A, B, C, şi n discuri de diametre diferite,


iniţial puse pe tija A astfel încât orice disc este
aşezat peste un disc de diametru mai mare
 Dacă numerotăm discurile în ordinea crescătoare a
diametrelor:
 discul 1 are diametrul cel mai mic, apoi discul 2, ş.a.m.d.,
discul n având diametrul cel mai mare,
 atunci discul 1 se află în vârf, discul n fiind la bază

76
 Se cere să se mute discurile de pe tija A pe tija B
respectând următoarele reguli:
 la fiecare pas se va muta un singur disc
 discurile vor forma secvenţe descrescătoare pe oricare
din cele 3 tije, deci peste discul k se pot aşeza doar
discurile 1, 2, …, k-1
 tija C va fi folosită ca şi tijă de manevră

 Se cere şi determinarea numărului de mişcări efectuate

 Problema se rezolvă recursiv în 3 etape, cunoscând


rezolvarea pentru (n-1) discuri

77
 Etapa1: Se deplasează discurile 1, 2, …, n-1 de pe tija A
pe C astfel:

78
 Etapa2: Discul n se mută de pe tija A pe tija B astfel:

79
 Etapa3: Se deplasează cele (n-1) discuri de pe tija C pe
B astfel:

80
 Practic, problema turnurilor din Hanoi cu n discuri s-a
redus la rezolvarea aceleiaşi probleme cu n-1 discuri, pe
care ştim să o rezolvăm dacă știm pentru n-2 discuri
ş.a.m.d., ştim să o rezolvăm dacă o ştim rezolva pentru
n=1

 Dar pentru n=1, practic se mută discul de pe tija sursă A


pe cea destinaţie B

81
 Cele 3 tije pot să fie în situaţiile:
 Sursă: discuri care trebuie transportate pe altă tijă
 Destinaţie: tija pe care se afla discurile transferate de pe
sursă
 Manevră: tija folosită pentru stocarea temporară a
discurilor

 Rolul tijelor se schimbă în cele 3 etape diferite ale procesului


de deplasare a discurilor

 Pentru rezolvare se defineşte o funcţie recursivă:


void hanoi(int n, char a, char b, char c);
 int n, numărul de discuri;
 char a, tija sursă;

 char b, tija destinaţie;

 char c, tija de manevră;

82
#include<iostream>
#using namespace std;

double mutari; // numar de mutari


void hanoi(int n, char a, char b, char c);
int main( ){
int n;
cout <<"Numarul de discuri este= ";
cin >> n;
mutari = 0;
hanoi(n,'A','B','C');
cout <<"Numarul de mutari este= " << mutari <<"\n";
cin.ignore();
cin.get();
} 83
void hanoi(int n, char a, char b, char c){
if (n==1) {
printf("Se muta discul 1 de pe tija %c pe tija %c\n",a, b);
mutari++;
cin.get();
return;
}
// n>1, Etapa 1, n-1 discuri, a-sursa, c-destinatia, b-manevra
hanoi(n-1, a, c, b);

// Etapa 2, discul n de pe tija a se muta pe b


printf(“Se muta discul %d de pe tija %c pe tija %c\n",n, a, b);
mutari++;
cin.get();

//Etapa 3, n-1 discuri, c-sursa, b-destinatia, a-manevra


hanoi(n-1,c, b, a);
} 84
 Maximul unui şir
 Abordarea recursivă:
 se descompune şirul în 2 subşiruri
 se determină maximul pentru fiecare subşir

 se determină maximul dintre cele două maxime

 pentru fiecare subşir se procedează în acelaşi mod până

când ajungem la un şir de lungime 1

85
 determinarea maximului unui şir cu ajutorul funcţiei
recursive m_max( ):
int m_max(int *vector, int pozi, int len);

 şirul este introdus de la intrarea standard (cu funcţia


citire_sir( )), şir cu o lungime mai mică decât o valoare
MAX dată
 şirul va fi afişat cu funcţia tip_sir( )

86
#include<iostream>
using namespace std;
#define MAX 8
void citire_sir(int*, int);
void tip_sir(int*, int);
int m_max(int*, int, int);
int main( ){
int n, Maxim;
int vect[MAX];
cout << "Numarul de elemente mai mic de " << MAX <<"
este=";
cin >> n;
cout << "Introduceti cele n elemente\n";
citire_sir(vect,n);
Maxim=m_max(vect, 0, n);
cout << "Maximul sirului este " << Maxim <<'\n';
cout <<"Tipareste elementele sirului\n";
87
tip_sir(vect,n);
cin.get(); }
int m_max(int *vector, int pozi, int len)
{
int a,b;
if(len == 1)
return *(vector+pozi);
else
{
a = m_max(vector, pozi, len/2);
b = m_max(vector, len/2+pozi, len/2+len%2);
if(a>b)
return a;
else
return b;
} 88

}
void citire_sir(int* v, int n)
{
// corp functie
}

void tip_sir(int* v, int n)


{
// corp functie
}

89
Ce stim dupa acest curs?
1. Cum se implementeaza in C/C++ un algortim recursiv?
2. Ce este o functie recursiva?
3. Ce este recursivitatea directa, respectiv cea indirecta?
4. Ce operatii au loc pe stiva la apelul unei functii?
5. Ce intelegem prin notiunea de stackoverflow?
6. Ce intelegem prin notiunea de time-out?
7. Cum se asigura iesirea din recursivitate?
8. Ce intelegem prin mecanismul de backtracking?
9. Pentru ce tip de probleme se aplica backtracking-ul?
10. Care sunt elementele de baza care se stabilesc la o pentru
rezolvarea unei probleme prin metoda backtracking?
11. Care este principiul metodei divide et impera?

90
91
PROGRAMAREA CALCULATOARELOR –
ALGORITMI

MODUL 1
CURS 3
3. TEHNICI DE CĂUTARE SI SORTARE
3.1. METODE DE CĂUTARE
 Obiectiv: Căutarea unor obiecte pe
baza valorii unui câmp (cheie) asociat
fiecărui obiect
- dacă obiectele nu sunt ordonate - căutare directă
- dacă obiectele sunt ordonate găsirea unui obiect se
poate face mai rapid

 Vom considera că obiectele sunt grupate în tablouri


unidimensionale şi că pentru cheie este definită o relaţie
de ordine

2
Căutarea binară
int CautareBinara(int *p, int n, int val) //iterativ
{
int inc, sfr, mij;
inc = 0;
sfr = n-1;
mij = (inc + sfr)/2;
while((inc <= sfr) && (val != p[mij]))
{
if(val < p[mij]) sfr = mij - 1;
else inc = mij + 1;
mij = (inc + sfr) / 2;
}
if(p[mij] == val)
return mij;
else
3
return -1;
}
int CautareBinara(int *p, int inc, int sfr, int val) //recursiv – divide et
// impera
{
int mij;
mij = (inc + sfr)/2;
if(p[mij] == val)
return mij;

if(inc <= sfr) {


if(p[mij] > val)
sfr = mij - 1;
else
inc = mij + 1;
return CautareBinara(p, inc, sfr, val);
}
return -1;
4
}
??? Dacă avem un șir ordonat de 1023 elemente, care
este numărul maxim de comparații care va fi făcut pentru
căutarea unei valori?

5
Funcţii de bibliotecă
 Biblioteca standard (search.h) pune la dispoziţie funcţii
pentru căutare

 In cazul unor vectori neordonaţi:

void *lfind(const void *key, const void *base,


size_t *num, size_t width,
int (*fcmp)(const void *, const void*));

void *lsearch(const void *key, void *base,


size_t *num, size_t width,
int (*fcmp)(const void *, const void *));
6
 În caz de succes funcţiile returnează adresa primului
element care are cheia căutată
 În caz de eşec:
 funcţia lfind( ) returnează pointerul null
 funcţia lsearch( ) adaugă elementul la sfârşitul tabloului
și returnează un pointer la elementul adăugat
 Funcţia de comparare fcmp( ) trebuie să returneze:
0, în caz de egalitate a elementelor
≠0, în caz contrar

• Ceilalți parametri sunt: key adresa cheii utilizate, base


adresa de început a tabloului, num adresa variabilei număr
de elemente, width dimensiunea unui element

 Versiunile mai noi pentru lfind() si lsearch() sunt _lfind() și


_lfind_s(), respectiv _lsearch() și _lsearch_s() (lfind() si 7
lsearch() nu mai sunt valabile in Visual Studio)
// exemplu cu _lfind( )
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h> %lld in Visual Studio 2022 si
#include <search.h> %d in Visual Studio 2019
#define DIM 10

int compare_int(int* a, int* b);

int main( ){
int int_values[DIM] = { 1, 3, 2, 4, 5 };
int* int_ptr, key_value, num = 5; // key_value este valoarea cautata
printf("Dati valoarea cautata: ");
scanf("%d", &key_value);
int_ptr = (int*)_lfind(&key_value, int_values, (unsigned*)&num, sizeof(int),
(int (*) (const void*, const void*)) compare_int);
if (int_ptr != 0)
printf("Numarul %d este pe poz. : %lld la adresa %p\n", key_value, int_ptr -
int_values + 1, int_ptr);
else
printf("Numarul %d nu este in tabel\n", key_value);
}//end main( )
int compare_int(int* a, int* b){
return(*a - *b);
}//end compare_int( )

9
#include<search.h>
#include<stdio.h>
#include<string.h>

#define DIM 12
int cmp(const char* arg1, const char* arg2);
int addelem(const char* key, const char** tab, int nelem);
int main(void){
const char* luni[DIM] = {"ian", "feb", "mar", "apr", "mai", "iun" };
int i, nluni=6;
const char* key = "iul";
if (addelem(key, luni, nluni))
printf("Luna %s este deja in tablou.\n", key);
else {
nluni++;
printf("Luna \"%s\" a fost adaugata in tablou : ", key);
for (i = 0; i < nluni; i++)
printf("%s, ", luni[i]);
} 1
} 0
int addelem(const char *key, const char **tab, int nelem)
{
int oldn = nelem;
_lsearch(&key, tab, (size_t *)&nelem, sizeof(char *),
(int(*)(const void *,const void *))cmp);
return(nelem == oldn);
}

int cmp(const char *arg1, const char *arg2)


{
return(strcmp(arg1, arg2));
} 11
 In cazul vectorilor ordonaţi (stdlib.h / search.h):

void *bsearch(const void *key, const void *base,


size_t nelem, size_t width,
int (*fcmp)(const void*, const void*));

 Parametrii au aceiași semnificație cu funcțiile precedente


doar că al 3-lea parametru e lungimea efectivă, size_t nelem
 Tabloul trebuie să fie ordonat crescător, iar funcţia de
comparare trebuie să returneze:
 o valoare negativă dacă *v1 < *v2
 zero dacă există egalitate între elemente
 o valoare pozitivă dacă *v1 > *v2

12
// utilizarea functiei de biblioteca bsearch()
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

int compare_int(int *a, int *b);


int compare_float(float *a, float *b);

int main(void){
int int_values[] = {1, 2 , 3, 4, 5};
float float_values[] = {1.1, 2.2, 3.3, 4.4, 5.5};

int *int_ptr, int_value = 2, num;


float *float_ptr, float_value = 33.3;
13
num = sizeof(int_values)/sizeof(int);
//apel la functia de bibioteca bsearch() pentru sirul de numere intregi
int_ptr = (int *)bsearch(&int_value, int_values, num, sizeof(int),(int (*)
(const void *, const void *)) compare_int);

if (int_ptr)
printf("Valoarea %d a fost gasita!\n", int_value);
else
printf("Valoarea %d nu a fost gasita!\n", int_value);

num = sizeof(float_values)/sizeof(float);

14
//apel la functia de bibioteca bsearch() pentru sirul de numere reale
float_ptr = (float *)bsearch(&float_value, float_values, num,
sizeof(float),(int (*) (const void *, const void *)) compare_float);

if (float_ptr)
printf("Valoarea %3.1f a fost gasita!\n", float_value);
else
printf("Valoarea %3.1f nu a fost gasita!\n", float_value);

}//end main()

15
int compare_int(int *a, int *b)
{
return(*a - *b);
} //end compare_int()

int compare_float(float *a, float *b)


{
if(*a < *b) return -1;
if(*a > *b) return 1;
return 0;
} //end compare_float()

16
Metode de sortare- Generalități
 Sortarea constă în rearanjarea obiectelor într-o ordine
specifică, prin permutarea acestora
 Pentru un set de obiecte S={a1, a2, ..., an}, prin sortare
rezultă setul S1={ak1, ak2, ..., akn}, astfel că, dându-se o
funcţie de ordonare f, este îndeplinită relaţia de ordine :
f(ak1) < f(ak2) <...< f(akn)
 Sortarea se face în raport cu o cheie asociată obiectelor
 Eficienţa unei metode de sortare se evaluează prin:
 numărul de comparaţii ale cheii
 numărul de permutări ale unui obiect

 Aceste operaţii sunt dependente de numărul de elemente


din set 17
CLASIFICAREA METODELOR DE SORTARE
Sortări prin interschimbare:
- Bubble Sort
- Cocktail Sort
- Comb Sort
- Quick Sort
Sortări prin selecție: - Selection Sort
- Heap Sort
Sortări prin inserție: - Insertion Sort
- Shell Sort
Sortări prin interclasare: - Merge Sort

Sortări fără comparații: - Radix sort


18
3.2.Metode de sortare
3.2.1. Metode simple de sortare

 Numărul de comparații este de ordinul n*n


 Sortarea se face "in situ" (pe loc)
 Dacă n este de ordinul sutelor sau chiar al miilor, timpii
de calcul sunt foarte apropiaţi pentru toate metodele de
sortare, astfel că, în acest caz, prevalează complexitatea
metodei de implementare.

19
 Sortarea prin interschimbare (bubble sort)

 se consideră perechi alăturate de elemente care se


prelucrează : (0,1), (1,2), (2,3),…
 dacă nu sunt în ordinea corectă, atunci elementele
perechii sunt schimbate între ele
 după prima parcurgere, cel mai mare element din vector
ajunge pe ultima poziţie

 se fac mai multe parcurgeri pentru a ordona întregul


tablou, iar la fiecare parcurgere tabloul prelucrat este mai
scurt, deoarece ultimele elemente sunt deja sortate

20
EXEMPLU BUBBLE-SORT

 Exemplu : 9 7 5 6 2
7 9 5 6 2 -> 7 5 9 6 2 -> 7 5 6 9 2 -> 7 5 6 2 9
5 7 6 2 9 -> 5 6 7 2 9 -> 5 6 2 7 9

5 6 2 7 9 -> 5 2 6 7 9

5 2 6 7 9 -> 2 5 6 7 9

 Observatie: după prima parcurgere a șirului pe ultima


poziție se găsește valoarea maximă din șir, la a doua
parcurgere pe penultima poziție se va găsi valoarea
maximă din subșirul rămas, etc.
21
void SortBubble(int *p, int n)
{
int i, j, temp;
for(i=0; i<n; i++) // parcurgeri
{
for(j=1; j<n-i; j++) // prelucrare sir curent
{
if(p[j-1] > p[j])
{
// interschimbare
temp = p[j-1];
p[j-1] = p[j];
p[j] = temp;
}
}
} 22
}
 Algoritmul poate fi îmbunătăţit dacă se observă că
după o parcurgere a tabloului în care nu s-a făcut nici
o interschimbare, sortarea este terminată

 Exemplu : 9 2 5 6 7:
 9 2 5 6 7 -> 2 9 5 6 7 -> 2 5 9 6 7 -> 2 5 6 9 7 -> 2 5 6 7 9
 25679

 Pentru aceasta se introduce o variabilă care să


semnalizeze interschimbarea

23
void SortBubble(int *p, int n)
{
int i, j, temp, flag;
for(i=0; i<n; i++) {
flag = 1;
for(j=1; j<n-i; j++) {
if(p[j-1] > p[j]) {
// interschimbare
temp = p[j];
p[j] = p[j-1];
p[j-1] = temp;
flag = 0;
}
}
// daca nu s-a facut nici o interschimbare
if(flag==1) break;
} 24
}
//bubble sort (varianta do-while)
void SortBubbleD(int *p, int n)
{
int j, temp, flag;
do{
flag = 1;
for(j=0; j<n-1; j++) {
if(p[j] > p[j+1]) {
temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
flag = 0;
}
}//for
}while(flag == 0);
}
25
 Sortarea prin selecţie simplă
 se caută cel mai mic element şi se aduce pe prima
poziţie din şir prin interschimbare
 apoi se consideră tabloul format din elementele
2,3,...,N şi se caută cel mai mic element care se
aduce pe prima poziţie din şirul curent, ...

 Exemplu : 9 7 5 6 2
 9 7 5 6 2 -> 2 7 5 6 9 -> 2 5 7 6 9 -> 2 5 6 7 9

26
void SortSel(int *p, int n){
int i, j, pozmin, temp;
for(i=0; i<n; i++) // parcurgeri
{
// cautare pozitie cel mai mic element din sirul curent
pozmin = i;
for(j=i+1; j<n; j++) {
if(p[pozmin] > p[j])
pozmin = j;
}
// interschimbare cu elementul de pe prima pozitie
temp = p[pozmin];
p[pozmin] = p[i];
p[i] = temp;
} 27

}
 Sortarea prin inserţie simplă
 se consideră pe rând tablourile formate din primele
2,3,...,N elemente din vector
 se asigură că aceste tablouri sunt ordonate prin
aducerea noului element (2,3,...) pe poziţia
corespunzătoare valorii sale
 acest lucru implică deplasarea spre dreapta, cu o
poziţie, a elementelor cu chei mai mari decât cea a
noului element, astfel ca acesta să ajungă înaintea
acelor elemente, dar după elementele cu chei mai mici

28
EXEMPLU INSERȚIE SIMPLĂ

 Exemplu : 9 7 5 6 2

97 -> 79
7 9 5 -> 579
5 7 9 6 -> 5679
5 6 7 9 2 -> 25679

29
void SortIns(int *p, int n){
int i, j, temp;
for(i=1; i<n; i++) {
temp = p[i];
for(j=i-1; j>=0; j--) // tabloul curent
{
if(p[j] > temp)
p[j+1] = p[j]; // deplasare dreapta
else
break;
}
p[j+1] = temp;
} 30
}
 Observaţie:
 poziţia pe care trebuie adus noul element este
căutată secvenţial (pe un subşir deja ordonat),
astfel că algoritmul se poate îmbunătăţi
(elementele analizate fiind deja sortate), folosind
căutarea binară

31
void SortIns(int *p, int n){
int i, j, temp, inc, mij, sfr;
for(i=1; i<n; i++) {
temp = p[i];
// cautarea binara
inc = 0; sfr = i-1;
mij = (inc + sfr)/2;
while(inc <= sfr) {
if(p[mij] > temp)
sfr = mij-1;
else
inc = mij+1;
mij = (inc + sfr)/2;
} // → toate valorile incepand cu poz inc sunt mai
mari decat p[i] → le deplasam spre dreapta
for(j=i-1; j>=inc; j--)
p[j+1] = p[j]; // deplasare dreapta
p[inc] = temp; 32
}
}
ALGORITMUL SHELL SORT
 ShellSort este un algoritm de sortare performant, bazat
pe sortarea prin inserție (InsertSort)

 Algoritmul lucrează pe tablouri de lungime N, fiind de


clasa O(N2)

 Cu toate acestea, algoritmul este vizibil mai rapid decât


algoritmii obișnuiți din clasa O(N2): InsertSort,
BubbleSort, SelSort, etc., fiind de circa 2 ori mai rapid
decât InsertSort, cel mai apropiat competitor din clasa
O(N2)

33
ALGORITMUL SHELL SORT
 Algoritmul ShellSort realizează deplasări pe distanţe
mari, sortând elementele aflate la distanţe mari prin
metoda inserţiei.

 După această sortare se continuă cu elemente aflate la


distanţe mai mici s.a.m.d. Se introduce noţiunea de n-
sortare: sortarea fiecărui al n-lea element.

 Diminuarea distanței se face folosind o secvenţă de


numere denumită şi secvenţă de intervale sau secvenţă
de spaţii.

 Uzual se utilizează secvenţa Knuth: h=3*h+1 (1, 4, 13,


40, 121, 364,…). Initial h se alege a.i. h > n/3 (unde n- nr
de elemente din sir). 34
Exemplu Shell Sort

h=4

7 10 1 9 2 5 8 6 4 3
* *
2 10 1 9 7 5 8 6 4 3
* *
2 5 1 9 7 10 8 6 4 3
* *
2 5 1 6 7 10 8 9 4 3
* * *
2 5 1 6 4 10 8 9 7 3
* * *
2 3 1 6 4 5 8 9 7 10
35
h=1
2 3 1 6 4 5 8 9 7 10
*
1 2 3 6 4 5 8 9 7 10
*
1 2 3 4 6 5 8 9 7 10
*
1 2 3 4 5 6 8 9 7 10
*
1 2 3 4 5 6 7 8 9 10

36
void ShellSort(int *p, int max){
int stop=0,temp=0,h=0, i, j , k;
while( h<= max/3)
h = 3*h + 1; // se porneste de la un h>max/3
while(h>0) {
for(int i=h; i<max; i++) { // se ia fiecare element de la pozitia h
// pana la sfarsit
temp=p[i];
j=i;
while((j>=h)&&(p[j-h]>temp)) {
p[j]=p[j-h]; // deplasare dreapta
j=j-h;
}
p[j]=temp;
} //for
h = (h-1)/3; // modificarea distantei de comparare
}// while 37
}
 Observaţii:
 toate exemplele anterioare realizează sortarea prin
ordonarea crescătoare a unor numere întregi
 Temă:
 modificaţi aceste funcţii de sortare pentru a realiza
sortări prin ordonare descrescătoare pentru toate
tipurile aritmetice

38
 Exemplul 1: utilizarea unei funcţii de sortare:
#include <iostream>
using namespace std;

void SortIns(int *p, int n);

int main( ){
int i, n, *tab;
cout <<"Cate numere: ";
cin >> n;

tab = new int[n];


if(tab != 0) {
cout << "Introduceti " << n << " numere intregi:" << endl;
for(i=0; i<n; i++){
cout << "\tNumarul "<<(i+1)<<": ";
cin >> *tab++; 39
}
tab -= n; //revin la inceputul tabloului
SortIns(tab, n);

cout << "\nNumerele sortate sunt: "<<endl;


for(i=0;i<n;i++)
cout << *tab++ <<" ";
cout << endl;

tab -= n; //revin la inceputul tabloului


delete [ ] tab;
}
}

void SortIns(int *p, int n)


{
// corp functie de sortare
} 40
 Exemplul 2
#include <iostream>
using namespace std;
void BubbleSort (const char **names, const int size);
int main(void) {
int dimc = 6;
const char *tabc[] = {"abc", "xyz", "acd", "axyz", "bc", "eltcti"};
BubbleSort(tabc, dimc); //sortare crescatoare dupa cod
cout << "\nSirurile sortate: ";
for(int i=0;i<dimc; i++)
cout << tabc[i] << ", ";
cout << endl;
}
41
void BubbleSort (const char **names, const int size) {
int swapped;
do {
swapped = 0;
for (int i = 0; i < size-1; ++i) {
if (strcmp(names[i], names[i+1]) > 0 ) {
char *temp = names[i];
names[i] = names[i+1];
names[i+1] = temp;
swapped = 1;
} // if
} // for
} while (swapped);
}

42
Exemplul 3
#include <iostream>
using namespace std;
int fcmp(const char *s1, const char *s2);
void BubbleSort (const char **names, const int size);
int main(void) {
int dimc = 6;
const char *tabc[] = {"abc", "xyz", "acd", "axyz", "bc", "eltcti"};
BubbleSort(tabc, dimc); //sortare crescatoare dupa dimensiune
cout << "\nSirurile sortate: ";
for(int i=0;i<dimc; i++)
cout << tabc[i] << ", ";
cout << endl;
}
43
void BubbleSort (const char **names, const int size) {
int swapped;
do {
swapped = 0;
for (int i = 0; i < size-1; ++i) {
if (fcmp(names[i], names[i+1]) > 0 ) {
const char *temp = names[i];
names[i] = names[i+1];
names[i+1] = temp;
swapped = 1;
}
}
} while (swapped);
}

int fcmp(const char *s1, const char *s2){


return(strlen(s1)-strlen(s2));
// return strcmp(s1, s2);
44
}
 Temă:
 modificaţi aceste funcţii de sortare pentru a include
funcţia de comparaţie la apelul funcţiei de sortare

void Sort(char **tab, int n, int(*fcmp)(const char *s1, const


char *s2));

45
46
3.2.2. Metode avansate de sortare

 Sunt algoritmi ce permit reducerea numărului de


comparaţii până la nlog(n)
 Prezintă o complexitate mai mare, exprimată prin
recursivitate, structuri de date specializate sau utilizarea
mai multor tablouri pentru sortare
 Eficienţa acestor algoritmi este evidentă pentru
dimensiuni mari, începând cu ordinul miilor (lucru ce
reiese şi din raportul n/log(n) )

47
SORTAREA PRIN INTERCLASARE (MERGE SORT)
 Principiul metodei: se bazează pe obținerea din 2 tablouri
ordonate a unui tablou ordonat ce conține elementele
celor două.
 Sortarea prin interclasare utilizează metoda Divide et
Impera
 Se împarte tabloul inițial nesortat în secvențe din ce în ce
mai mici, astfel încât fiecare secvență să fie ordonată la
un moment dat și apoi interclasată cu o altă secvență din
tablou.
 Practic interclasarea va începe când se ajunge la o
secvență formată din două elemente. Aceasta odată
ordonată se va interclasa cu o alta corespunzătoare, iar
apoi procesul va continua la următorul nivel ce implică
perechi de câte 4 elemente, etc.
48
49
#include<iostream>
using namespace std;
void interclas(int *a,int i,int m,int j);
// i- pozitia de inceput a primului subsir, m- pozitia de sfarsit a primului
subsir,
// j- pozitia de sfarsit al celui de-al doilea subsir
void divimp(int *a,int i,int j);
// i- indicele primului element din vector,
// j - indicele ultimului element din vector
#define DIM 1000
int main(){
int a[DIM],n;
cout<<"n="; cin>>n;
for(int i=0;i<n;i++){
cout<<"a["<<i<<"]=";
cin>>a[i];
} 50
divimp(a,0,n-1);
for(int i=0;i<n;i++)
cout<<a[i]<<' ';
} //main

void divimp(int *a,int i,int j)


{
if (i<j)
{
int m=(i+j)/2;
divimp(a,i,m); // apel functie pentru prima jumatate a sirului
divimp(a,m+1,j); // apel functie pentru a doua jumatate a sirului
interclas(a,i,m,j); // interclasarea celor 2 subsiruri
}
}
51
void interclas(int *a,int i,int m,int j) {
int b[DIM];
int x=i; // pentru deplasarea in primul subsir
int k=0;
int y=m+1; // pentru deplasarea in al doilea subsir
while(x<=m && y<=j)
if (a[x]<a[y])
b[k++]=a[x++];
else
b[k++]=a[y++];
while (x<=m)
b[k++]=a[x++]; // restul elementelor din primul subsir
while (y<=j)
b[k++]=a[y++]; // restul elementelor din al doilea subsir
int t=i;
for (k=0;k<(j-i)+1;k++)
a[t++]=b[k]; // mut elementele sortate in sirul initial 52
}
Observații:
 numărul de operații elementare executate este de ordinul
O(nlog(n)).
 pentru tablouri cu număr mare de componente, timpul de
calcul este mult mai mic în cazul sortării prin interclasare,
decât în cazul folosirii algoritmilor simpli, cum ar fi cel al
selecţiei, inserţiei sau al bulelor, a căror complexitate
este O(n2).
 Algoritmul de sortare prin interclasare consumă însă de
două ori mai multă memorie decât cei simpli menționaţi,
deoarece necesită spaţiu suplimentar pentru tabloul
auxiliar .

53
 Algoritmul QuickSort
 Se bazează pe noţiunea de partiţie
 Prin partiţionarea datelor se înţelege împărţirea
acestora în două grupuri, astfel încât toate elementele
cu chei mai mari decât o valoare dată se află într-un
grup, iar cele cu valori mai mici se află în celălalt grup

 Etapele partiţionării :
 se consideră un element x al tabloului numit şi element
pivot
 se parcurge tabloul de la stânga până ce se găseşte un
element ai mai mare ca x
 se parcurge tabloul de la dreapta până ce se găseşte un
element aj mai mic ca x

54
 se interschimbă ai cu aj
 se actualizează i şi j prin incrementare, respectiv
decrementare
 se repetă paşii anteriori până când cele două scanări se
întâlnesc
 în acest moment tabloul este partiţionat, adică :
 în stânga lui x se găsesc numai elemente mai mici ca x
 în dreapta lui x se găsesc numai elemente mai mari ca x
 a[k] <= x, k = 0,...,i-1
 a[k] >= x, k = j+1,...,n
 a[k] = x, k = j+1,...,i-1

55
 După prima partiţionare, şirul nu este încă ordonat
 De aceea se continuă, la stânga şi la dreapta pivotului,
în acelaşi mod.
- algoritmul QuickSort are o natură recursivă
Descriere in pseudocod:

QuickSort is {
If (right-left) == 0 then
Return
Else
pivot = Tablou[right]; // sau Tablou[middle]
partition = Partitionare(left, right, pivot)
QuickSort(left, partition-1);
QuickSort(partition+1, right);
EndIf
} 56
 Pivotul este elementul din mijlocul sirului

88 6 57 71 60 42 83 73 48 65

48 6 57 71 60 42 83 73 88 65

48 6 57 42 60 71 83 73 88 65

6 48 57 42 60 71 65 73 88 83

6 48 42 57 60 65 71 73 83 88

6 42 48 57 60 65 71 73 83 88
Pivotul este ultimul element din șir
88 6 57 71 60 42 83 73 48 65
inc sfr

65 6 57 71 60 42 83 73 48 88
inc

65 6 57 48 60 42 83 73 71 88
inc sfr

42 6 57 48 60 65 83 73 71 88
sfr inc inc

42 6 57 48 60 65 71 73 83 88
sfr inc

6 42 57 48 60 65 71 73 83 88

6 42 57 48 60 65 71 73 83 88

6 42 48 57 60 65 71 73 83 88
void QuickSort(int *p, int prim, int ultim)
{
int inc, sfr, pivot, temp, mij;
inc = prim;
sfr = ultim;
mij=(prim+ultim)/2;
pivot = p[mij];
// partitionare
do {
while(p[inc] < pivot)
inc++;
while(p[sfr] > pivot)
sfr--;
59
if(inc < sfr) {
temp = p[inc];
p[inc] = p[sfr];
p[sfr] = temp;
}
if(inc <= sfr) {
sfr--;
inc++;
}
}while(inc <= sfr);
// apel recursiv
if(prim < sfr)
QuickSort(p, prim, sfr);
if(inc < ultim)
QuickSort(p, inc, ultim);
60
}
 Alegerea valorii pivot :
 pivotul trebuie să reprezinte valoarea cheii unui element
din tablou
 se poate alege la întâmplare, dar trebuie evitate
valoarea minimă, respectiv maximă

 Valori uzuale :
 elementul din mijlocul tabloului
 ultimul element din tabloul partiţionat
 valoarea mediană între primul, ultimul şi elementul din
mijlocul tabloului

61
 Biblioteca standard (stdlib.h) pune la dispoziţie funcţia:
void qsort(void *base, size_t nelem, size_t width,
int(*fcmp)(const void *, const void *));

 Funcţia de comparare, definită de utilizator pentru


sortarea crescătoare trebuie să returneze :
 < 0 dacă *v1 < *v2
 0 dacă *v1 == *v2
 > 0 dacă *v1 > *v2

 Pentru ordonare descrescătoare, valorile negativă și


pozitivă returnată se vor inversa. Tabloul unidimensional
de ordonat poate să conţină şi alte tipuri de date (şiruri de
caractere sau structuri).
62
// Exemplu tablou de intregi ordonat cu qsort()
#include <stdio.h>
#include <stdlib.h>

int compara(const void* val1, const void* val2);


int (*fcmp)(const void*, const void*);
#define DIM 20

int main(void){
int i,n,tab[DIM];
printf("Introdu dimensiune tablou: ");
scanf("%d",&n);
printf("Introdu elemente tablou:\n");
for(i=0;i<n;i++){
printf("tab[%d] = ",i);
scanf("%d",&tab[i]);
}
fcmp=compara; //fcmp va avea adresa functiei de comparare 63
qsort(tab,n,sizeof(i),fcmp);
puts("Tabloul ordonat este");
for(i=0;i<n;i++)
printf("%d ",tab[i]);
}//main
int compara(const void * val1, const void* val2)
{
//return((*(int*)val1) - (*(int*)val2)); //ordonare crescatoare
return((*(int*)val2) - (*(int*)val1)); //ordonare descrescatoare
} //compara

64
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

struct datac {
int an;
int luna;
int zi;
};
struct pers {
char numep[12];
struct datac datan;
};
65
int cmp( const struct pers *a, const struct pers *b);
int main(void){
struct pers angaj[ ] = {
{"x1", {1980, 6,6}},
{"x2", {1960, 5, 5}},
{"x3", {1960, 1,5}},
{"x4", {1961, 12, 32}},
{"x5", {1980, 2, 29}}
};
int i;
int nang = sizeof(angaj)/sizeof(struct pers);
// apel functie de sortare
qsort((void *)angaj, nang, sizeof(angaj[0]), (int (*)(const void*, const
void*))cmp);
printf("Datele sortate :\n");
for (i = 0; i < nang; i++) {
printf("\t%s, %d, %d, %d\n", angaj[i].numep, angaj[i].datan.an,
angaj[i].datan.luna, angaj[i].datan.zi);
}
66
}
int cmp(const struct pers *a, const struct pers *b){
if((a->datan).an > (b->datan).an) return 1;
else
if((a->datan).an < (b->datan.an)) return -1;
else {
if((a->datan).luna > (b->datan).luna) return 1;
else if((a->datan).luna < (b->datan).luna) return -1;
else {
if((a->datan).zi > (b->datan).zi)
return 1;
else if((a->datan).zi < (b->datan).zi)
return -1;
else
if((strcmp(a->numep, b->numep)>0)) return 1;
else if((strcmp(a->numep, b->numep)<0))
return -1;
return 0;
} //else 67
}// else
}
68
PROGRAMAREA
CALCULATOARELOR 2
Modul 2, partea I
Curs 4
1
CUPRINS

1. Concepte ale programării obiectuale


2. Clase şi obiecte:
• Noţiuni introductive
• Declararea unei clase
• Accesul la membri
• Autoreferinţa
• Constructori şi destructori
• Constructor de copiere
• Constructor cu conversie de tip

2
1. CONCEPTE ALE PROGRAMĂRII OBIECTUALE
DE CE POO (PROGRAMARE ORIENTATĂ PE OBIECTE)?
 Orice program este compus din 2 părți distincte:
- date (de intrare și/sau iesire) → tipuri de date
- operații → funcții
 Tipuri de date utilizate în programare:
- tipuri predefinite
- tipuri de date utilizator (în C/C++ prin utilizarea struct,
union, enum și typedef); operațiile acționează tot asupra
unor tipuri predefinite de date, elementele componente nu
sunt protejate → programarea structurată
- clasa: tip abstract, reunește date și operații în cadrul
aceluiași tip de dată → Programarea ADT (Abstract Data
Type – abstractizare a datelor) 3
CONCEPTE ALE PROGRAMĂRII OBIECTUALE

 Programarea orientată pe obiecte (POO) este un concept


foarte natural. În jurul nostru sunt o multitudine de
obiecte, interconectate între ele, comunicând unele cu
altele într-un fel sau altul – obiecte fizice (reale) –
complexe.
 Aceste obiecte pot fi reprezentate în calculator prin
obiecte calculator – mult mai simple, reprezentate prin
date concrete și operații specifice.
 POO a apărut pentru a elimina dezavantajele programării
structurate, în ceea ce privește tratarea separată a
algoritmilor și a structurilor de date ce se prelucrează,
posibilitatea reutilizării programelor, scalabilității unor
4
module de program.
 Ideea de bază de la care pleacă programarea orientată
pe obiecte este de a grupa structurile de date cu
operațiile care prelucrează respectivele date. Un
asemenea ansamblu poartă denumirea de clasă.

 Proiectarea de programe utilizând clase se numește


programare prin abstractizarea datelor ADT, iar dacă se
consideră și mecanismul moștenirii avem programare
orientată pe obiecte (POO).
Date + Metode = Clasa

 În mod frecvent, pentru structurile de date se utilizează


denumirea de date membre, câmpuri sau atribute, iar
pentru procedurile ce prelucrează aceste date, termenul
de funcții membre sau metode.
5
 Primul limbaj orientat pe obiecte este Simula 67,
dezvoltat în cadrul Norwegian Computing Centre. (anii
‘60)
 Alan Kay este considerat promotorul programării
orientate pe obiecte
 Simula a rămas un instrument în dezvoltarea de noi
limbaje, cele mai importante în anii 70-80 fiind Smalltalk,
Eiffel și C++.
 În acest moment cele mai folosite limbaje obiectuale sunt
derivate din C++, și anume C#, Java, Python, PhP, etc.

6
OBIECTELE
 Ce este un obiect? Un obiect modelează o entitate din
lumea reală sau imaginară.

 Fiecare obiect este unic. Puterea unui obiect e dată de


faptul că poate fi definit și menținut independent de
celelalte obiecte.

 Obiectele pot fi distinse între ele pentru că fiecare obiect


posedă un set de caracteristici unice (prin valori concrete).

 Reprezentarea obiectelor și a comportamentului lor


reprezintă obiectivul principal al analizei și proiectării
orientate spre obiecte.
7
 Definiția obiectelor după Grady Booch: un obiect este
complet caracterizat de 3 elemente:
- starea sa,
- comportamentul său
- identitatea sa.

Starea = setul de date care conțin informații referitoare la


obiect (structura obiectului și valorile sale curente).

Comportamentul = serviciile pe care le pune la dispoziție,


modul în care interacționează cu alte obiecte

Identitatea = un cod unic, ce îl diferențiază de alte obiecte

8
 Clasa = un șablon pentru generarea unor obiecte având
structura și comportamentul identice

 Clasa (conf. OMG) = o implementare care poate fi


instanțiată în vederea creării de multiple obiecte având
același comportament. Un obiect este o instanță a unei
clase.
- există și clase neinstanțiabile, numite abstracte, utilizate
în special în derivare

 Interfața unei clase = implementarea întregului său


comportament. Corespunde metodelor clasei care pot fi
utilizate de clienți ai clasei (publice)

9
 Mesajele fac apel la serviciile pe care obiectul le poate
oferi. Obiectele comunică prin intermediul mesajelor
(emițătoare și receptoare). Mesajul - obiect, numele
metodei și parametrii care rafinează mesajul.

 Metodele sunt secvențe de cod care implementează


mesajele. Metodele au acces direct la starea obiectului (la
date)
 Ex: clasa Student (nume, prenume, facultate, anul, grupa,
data_nașt., note, etc.).
- Mesaje posibile: Care este numele? Care este grupa?
Care este media anuală? etc.
- Servicii (metode) : furnizează toate detaliile despre
student, furnizează vârsta, afișează grupa, schimbă grupa,
furnizează media, etc. 10
Principiile de bază ale programării
orientate pe obiecte

11
1. Încapsularea datelor:
 gruparea datelor şi a operaţiilor permise asupra acestora
 ascunderea informaţiei (pentru protejarea împotriva
distrugerii accidentale).
 accesul la date prin intermediul metodelor
 asigurată prin specificatorii de acces

2. Abstractizarea datelor:
− identifică proprietățile generale și esențiale, omițând
unele detalii neesențiale (Ex. – carnet șofer)
 un obiect este caracterizat complet de specificaţiile
metodelor sale, detaliile de implementare fiind
transparente pentru utilizator (principiul lui Parnas)
 modificarea structurii datelor membre are efect numai
asupra metodelor, iar dacă se păstrează specificaţiile
metodelor, nu afectează utilizarea obiectului 12
 Pot exista la un moment dat mai multe obiecte de acelaşi
tip, adică instanţe ale aceleiaşi clase:
 instanţele obiect vor avea date diferite, dar vor partaja
acelaşi cod corespunzător metodelor clasei din care
provin

 Dacă există o anumită clasă şi cineva doreşte alta, uşor


diferită, nu este nevoie de modificarea primei clase, ci se
poate defini una nouă, care să fie ca şi clasa veche şi să
aibă unele caracteristici suplimentare prin ceea ce se
cheamă derivare sau moştenire.

13
3. Moștenirea
 Clasa originală se numește clasa de bază sau
superclasă, iar noua clasă - clasa derivată sau sub-clasă.

 Putem considera că toate clasele de obiecte sunt derivate


dintr-o clasă inițială, clasa obiectelor generice.

 Ierarhizarea poate fi realizată în continuare sub forma


unui arbore pe mai multe niveluri.

 Sunt clase (Ex. OM) ce nu pot fi instanțiate. Acestea sunt


clasele Abstracte.
- ele vor conține cel puțin o metodă abstractă. O
metodă abstractă e doar declarată, fiind definită în altă(e)
clasă(e).
14
4. Polimorfismul
 comportament diferit în funcţie de context
 posibilitatea de a opera cu diverse variante ale unor
funcţii ce realizează aceeaşi operaţie în mod specific
pentru diferite obiecte dintr-o ierarhie de clase

 Polimorfism
- static: supraîncărcarea funcțiilor (overloading)
- dinamic: existența unor funcții cu același nume și aceeași
semnătură în clase aflate într-o ierarhie de moștenire –
redefinire (overriding)

15
 POO :
 definire de tipuri abstracte (ADT) +
 derivarea tipurilor +
 [polimorfism (inclus de obicei în ADT și
derivare)]

 Principalele avantaje ale POO sunt :


 uşurinţa proiectării şi reutilizării codului
 fiabilitate crescută (codul odată testat nu mai
generează erori)
 uşurinţa înţelegerii
 creşterea abstractizării prin descompunerea în
subprobleme
 capacitatea de încapsulare: prin combinarea datelor şi
a operaţiilor pe date
 ascunderea informaţiei: se tratează operaţiile ca şi
16
cutii negre
Clase și obiecte

17
Noţiuni introductive

 Clasele permit introducerea unor tipuri noi de date

 Un tip de dată constă dintr-o reprezentare concretă a


obiectelor din acel tip, precum şi dintr-un set de operaţii
ce manipulează aceste obiecte:
 alte operaţii decât cele specificate nu sunt permise
asupra obiectelor

 Ca urmare, aceste operaţii caracterizează acel tip


 de obicei, structura internă a obiectelor este ascunsă
pentru operaţiile ce nu aparţin acelui tip

18
 Avem de-a face cu tipuri de date abstracte

 D.p.d.v. al limbajului C++, o clasă este un nou tip de dată

 După definirea unei clase, compilatorul o tratează ca şi


cum ar fi un tip de dată predefinit, inclusiv prin verificarea
erorilor

19
Declararea unei clase

 Declaraţia unei clase conţine un antet şi un corp:


 antetul specifică numele clasei şi eventual clasele de
bază
 corpul clasei conţine membrii clasei
 Membrii pot fi :
 date membre -> specifică reprezentarea obiectelor
clasei:
 sintactic acestea se declară ca şi variabilele obişnuite

 metode (funcţii) membre (de interfaţă) -> specifică


operaţiile clasei:
 sintactic acestea sunt ca şi prototipurile funcţiilor obişnuite

20
Specificator de acces
Antet clasa class <nume_clasa>
{
[private:]
Sectiune
[declaratii de date]
[declaratii de functii]
[protected:]
Sectiune
Corp clasa [declaratii de date]
[declaratii de functii]
[public:]
[declaratii de date]
Sectiune
[declaratii de functii]
} [lista de obiecte];

21
 Membrii unei clase pot aparţine unor secţiuni delimitate de
etichete de acces sau specificatori de acces:
 public, private şi protected

 Specificatorii de acces:
 permit ascunderea datelor şi metodelor
 ordinea în care apar este nerelevantă
 dacă nu este prezent nici un specificator de acces toate
datele membre şi toate metodele membre se consideră
că sunt private

22
 Un membru care este public poate fi utilizat de orice altă
funcţie (din clasă sau din afara clasei)

 Un membru care este privat poate fi utilizat doar de


metodele membre ale clasei sau de funcţiile prietene
clasei

 Un membru cu atributul protected are aceleaşi


caracteristici ca şi unul privat, în plus poate fi utilizat de
metode membre sau funcţii prietene ale claselor derivate,
dar numai în obiecte de tipul derivat:
 membrii de acest tip se folosesc în cazul derivării
claselor
23
 De obicei, datele unui obiect sunt plasate în secţiunea
private:
 sunt accesibile direct numai metodelor din clasă
 se permite accesul din exterior numai prin intermediul
metodelor publice
 care devin astfel o interfaţă la datele obiectului

 Exemplu:
class Rectangle
{ Datele (campurile)
int x, y; sunt private
public:
void setVal(int, int);
Metodele sunt
int aria(void) { publice
return x*y; }
}; 24
Criterii de calitate ale unei clase (Bjarne Stroustrup)
 O clasă trebuie să ofere un set mic, dar suficient și bine
definit de operații publice
 O clasă trebuie să poată fi privită ca și o cutie neagră,
manevrabilă prin setul de operații
 O clasă trebuie să aibă un set restrâns de date publice, pe
cât posibil, niciuna
 O clasă trebuie să fie ușor modificabilă, fără ca acest lucru
să se răsfrângă asupra interfeței publice (cu excepția
extinderii acesteia)

25
Accesul la membrii unei clase

26
Accesul la membrii unei clase

 Obiectul: o instanţă a unei clase


 Declararea unui obiect se poate face:

 în momentul declarării clasei (între acolada din dreapta


şi terminatorul ;)
 ulterior, printr-o declaraţie separată

 Declararea ulterioară a unui obiect se face astfel:


Nume_clasa nume_obiect;
Nume_clasa lista_nume_obiecte;

27
 Un obiect poate fi privit ca o generalizare a noțiunii de
variabilă, astfel încât toate aspectele referitoare la
variabile sunt păstrate:
- din punct de vedere al accesului avem obiecte
locale și globale
- din punct de vedere al alocării avem obiecte statice,
automatice și dinamice

28
 Accesul din exterior la membrul public al unui obiect (dată
sau funcţie/metodă) se face cu operatorul punct(.) :
nume_obiect.data_membra
nume_obiect.metoda_membra(lista_param_actuali)

 Se pot declara şi pointeri la obiecte :


// declarare si initializare pointer
Nume_clasa *nume_pointer;
nume_pointer = &nume_obiect;
sau
Nume_clasa *pobj = new Nume_clasa(…); //constructor
 Accesul pe baza pointerilor se face similar cu structurile
(folosind operatorul →), în modul următor :
nume_pointer -> data_membra
29
nume_pointer -> metoda_membra(lista_param_actuali)
 Putem să declarăm și obiecte de tip referință, caz în care
trebuie inițializate la declarare.

// declarare si initializare referință


Nume_clasa &nume_referință=nume_obiect;

 Accesul pe baza referinței se face tot cu operatorul “.” în


modul următor :
nume_referință.data_membra
nume_referință. metoda_membra(lista_param_actuali)

30
Definirea metodelor

31
Definirea metodelor
 Dacă definirea metodei este realizată în interiorul clasei,
sintaxa definiţiei este identică cu cea a oricărei funcţii
întâlnite în programarea non-obiectuală
 În acest caz avem aşa numitele metode inline pentru care
compilatorul va încerca o implementare inline:

class nume_clasa{

// metoda inline
tip_returnat nume_metoda (lista_parametri_formali)
{
// corp metoda
}

32
} [lista_de_obiecte];
 O altă metodă de descriere a unei clase este de a declara
doar prototipul metodelor membre în interiorul clasei şi de a
le defini funcţionalitatea lor în exteriorul zonei de cod
alocate acesteia
 În această situaţie, în momentul implementării corpului
metodei respective, trebuie să specificăm numele clasei
căreia îi aparţine metoda al cărei conţinut urmează să fie
scris
 Pentru aceasta se foloseşte operatorul de rezoluţie sau
scop, simbolizat prin ::

tip_returnat Nume_clasa :: nume_functie (lista_param_formali)


{
// corp metoda
} 33
class Rectangle
{
int x, y;
public:
void setVal(int, int); // declaratie
// metoda inline
int aria(void) { return x*y; }
}; // Rectangle

void Rectangle::setVal(int a, int b) // implementare in exteriorul clasei


{
if (a>0 && b>0) { x = a; y = b; }
else { x = 0; y = 0; }
}

34
int main (void){
Rectangle dr1, dr2; // declarare obiecte
dr1.setVal(4,5); // apel metoda
dr2.setVal(3,4); // apel metoda
cout<< “Aria obiectului 1: “ << dr1.aria( ); // apel metoda
cout<< “\nAria obiectului 2: “ << dr2.aria( ); // apel metoda

Rectangle *pdr; // declarare pointer la tipul Rectangle


pdr = &dr1; // initializare pointer
pdr->setVal(6,3); // apel metoda
cout<< “\nAria obiectului 1: “<< pdr->aria( ); // apel metoda
pdr = &dr2;
pdr->setVal(5,4);
cout<< “\nAria obiectului 2: “<< pdr->aria( );

Rectangle &ref_dr=dr1; // declarare si initializare


// referinta de tipul Rectangle
ref_dr.setVal(10,5); // apel metoda
cout<< "\nAria obiectului 1: "<< ref_dr.aria( ); // apel metoda 35

}
 Operatorul de rezoluţie mai poate fi folosit pentru a distinge
între datele membre şi variabile locale cu acelaşi nume
dintr-un bloc (funcţie sau instrucţiune compusă) :

class Point
{
private:
int x, y;
public:
void setVal(int x, int y)
{
Point::x = x;
Point::y = y;
}
}; Parametrii
Date ale clasei metodei 36
class Access {
int nA;
int getA( ) { return nA; }
private: // redundant, specificatorul implicit e private
int nB;
int getB( ) { return nB; }
protected:
int nC;
int getC( ) { return nC; }
public:
int nD;
int getD( ) { return nD; }
};

37
int main( )
{
Access cAccess;
cAccess.nD = 5; // corect
std::cout << cAccess.getD( ); // corect

cAccess.nA = 2; // gresit, e private


std::cout << cAccess.getB( ); // gresit, e private

cAccess.nC = 7; // gresit, e protected


std::cout << cAccess.getC( ); // gresit, e protected

38
 Pentru accesul la date putem utiliza funcții de tip accesor
(care returnează valoarea unei date membre – getter)
sau funcţii de tip mutator (care setează valoarea unei
date membre – setter)
class Date{
private:
int nMonth;
int nDay;
int nYear;
public:
// getters
int getMonth( ) { return nMonth; }
int getDay( ) { return nDay; }
int getYear( ) { return nYear; }
// setters
void setMonth(int Month) { nMonth = Month; }
void setDay(int Day) { nDay = Day; } 39
void setYear(int Year) { nYear = Year; }
};
 Avantaje:
 Dacă se doreşte modificarea datelor membre, se
modifică funcţiile accesor și/sau mutator şi, atât timp cât
nu se modifică specificaţiile acestora, utilizarea acestor
metode nu se modifică

 Utilizatorii clasei nu au nevoie să cunoască detaliile de


implementare (date membre, alte funcţii private), ci doar
interfaţa (funcţiile accesor și mutator)

 Folosirea acestor funcţii accesor/mutator asigură o


protecţie a datelor membre:
 Nu permit modificări accidentale sau nepermise
 Asigură şi alte modificări alte altor date membre corelate

 Permit validarea valorilor folosite pentru modificarea


datelor membre
40
• Se simplifică depanarea programului, deoarece
modificarea datelor membre se face controlat, prin
intermediul unei singure funcţii

 Uzual se separă declaraţia claselor de definiţia acestora


şi de fişierele unde sunt utilizate
 Astfel se crează fişiere antet (.h) ce conţin declaraţiile
claselor şi fişiere sursă (.cpp) ce conţin definiţiile claselor

41
Structuri și reuniuni în C++

42
STRUCTURI ÎN C++
 În limbajul C structurile au doar date membre
 În limbajul C++, o structură e asimilată unei clase, toți
membrii fiind implicit public:
 o structură în C++ poate include metode

struct Point { class Point {


int x, y; public:
Point(int, int); int x, y;
void offsetPt(int, int); Point(int, int);
}; void offsetPt(int, int);
};//class Point
Celelalte consideratii privind structurile sunt păstrate în
C++ (acces la membri, inițializare, etc.). In clase de regula43
datele se protejează
REUNIUNI ÎN C++
 O reuniune, union, în C++ e considerată o clasă având
datele memorate în aceeași zonă de memorie a unei
instanțe (a unui obiect):
 Dimensiunea unui obiect va fi definită de cea mai mare
dimensiune a datelor membre
 Utilizarea reuniunilor e realizată în cazul în care un obiect
are valori de tipuri diferite la momente diferite, dar de un
singur tip la un moment dat
 Toți membrii unei reuniuni sunt considerați implicit public

 Într-o reuniune și într-o structură putem considera


specificatorii de acces public, private și protected pentru a
controla accesul la membrii structurii sau reuniunii
44
Autoreferința

45
Autoreferinţa

 Pentru a defini metodele (funcţiile) membre trebuie


făcute referiri la datele membre fără a specifica un obiect
anume

 La apelul unei metode, aceasta este informată asupra


identităţii obiectului asupra căruia va acţiona prin
transferul unui parametru implicit, care reprezintă adresa
acelui obiect

 Acest parametru se numeşte autoreferinţa sau pointerul


this= adresa obiectului curent
46
 În definiţia unei funcţii membre, este permis accesul la
această adresă folosind cuvântul cheie this:
 acesta este un pointer la obiectul pentru care s-a apelat
funcţia şi corespunde următoarei declaraţii:
Nume_clasa *const this;

 În interiorul clasei, accesul la o variabilă se poate face, în


afară de modul prezentat în exemplele anterioare, cu
ajutorul pointer-ului this:
this->x=0;  x=0;

47
 Utilizări ale autoreferinţei :

 în cazul în care avem de-a face în interiorul unei funcţii


dintr-o clasă cu alte instanţe ale clasei:
 this ajută la diferenţierea variabilelor din clasă în ceea ce
priveşte obiectele de care aparţin
 pointerul în discuţie va indica întotdeauna obiectul curent

 pentru a verifica dacă parametrul unei metode este


chiar obiectul însăşi:

int MyClass::isthis(MyClass param)


{
if(&param == this) return 1;
else return 0;
} 48
 Utilizări ale autoreferinţei :
 în metode ce supraîncarcă operatorul de atribuire
(asignare) = , caz în care se returnează obiectul curent
(vom vedea cum la capitolul supraincarcarea operatorilor)
 în clase în care parametrul unei metode are același
nume cu un atribut al clasei :

class X{
int a; Atribut (camp)
public: al clasei
X(int a){
this->a=a; // sau X::a = a;
}
…}; Parametrul
metodei 49
EX. POINTERUL THIS
#include <iostream>
using namespace std;
class TimeClass {
int hours, minutes;
public:
TimeClass ( int new_hours, int new_minutes) // constructor
{ hours = new_hours; minutes = new_minutes; }
void setTime ( int new_hours, int new_minutes )
{ hours = new_hours; minutes = new_minutes; }
void getTime ( int &current_hours, int &current_minutes){
current_hours = hours; current_minutes = minutes; }
void copyTime( TimeClass &Time2);
};//TimeClass
50
void TimeClass::copyTime (TimeClass &Time2){
this->hours=Time2.hours;
this->minutes=Time2.minutes;};

int main(void){
TimeClass time1 (11,25), time2 (12,12),time3(1,1); // instantieri
int hrs, mins;
time1.getTime(hrs, mins);
cout<<"time1 is: " <<hrs<< ":" <<mins<<endl;
Time2.getTime(hrs, mins);
cout<<"time2 is: " <<hrs<< ":" <<mins<<endl;
time3.copyTime(time2);
time3.getTime(hrs, mins);
cout<<"\nAfter copying time2 to time3, the time is :
"<<hrs<<":"<<mins<<endl;
51
}//main
Constructori şi destructori

52
 Printre funcţiile membre ale unei clase există două tipuri
particulare: constructori şi destructori

 Constructorul are rolul de a crea şi inţializa un obiect al


clasei
 are acelaşi nume ca şi clasa căreia îi aparţine

 Destructorul are rolul de a distruge obiecte create de


către constructor
 numele destructorului este compus din caracterul
tilda(~) urmat de numele clasei căreia îi aparţine

53
 Teoretic constructorii și destructorii pot fi publici sau
privaţi, dar uzual se folosesc ca membri publici

 Aceste metode efectuează operaţii prealabile utilizării


unui obiect, respectiv distrugerii acestuia

 Alocarea şi eliberarea spaţiului de memorie necesar


datelor membre este făcută de către compilator (a nu se
confunda cu alocarea dinamică la iniţiativa
programatorului)

54
 Caracteristici comune constructori/destructori :

nu returnează valori; nu se specifică void în declaraţii


nu pot fi moşteniţi, dar o clasă derivată poate apela
constructorul/destructorul clasei de bază
nu se poate obţine adresa acestor funcţii
dacă nu sunt definiţi explicit, sunt generaţi automat de
către compilator, caz în care vor avea specificatorul de
acces public (impliciți)

ca orice alte metode, constructorii și destructorii pot


implementati in clasă sau doar declaraţi în interiorul clasei
şi apoi implementaţi în exteriorul acesteia (utilizând
operatorul de rezoluţie :: ) 55
 Caracteristici specifice constructorilor :
❑ constructoriipot avea parametri, parametri impliciţi şi pot
fi supraîncărcați (overloading)
▪ constructorul explicit vid, este constructorul care nu
are nici un argument; de obicei, aceste funcţii
iniţializează variabilele din clasă cu valori implicite (0,
NULL, etc.)
▪ constructorul explicit cu parametri, este acel
constructor care iniţializează anumite variabile din
clasă şi/sau efectuează anumite operaţii asupra
variabilelor primite ca parametri. Putem avea
constructori expliciți cu toti parametrii impliciți, o parte
dintre ei sau nici un parametru implicit.

❑ compilatorul apelează automat constructorul la 56

definirea obiectelor
class Rectangle {
int x,y;
public:
// constructor explicit vid
Rectangle(){
x=0; y=0; }
//constructor explicit cu parametri
Rectangle(int a, int b) {
x = a; y = b;
}
};
sau
class Rectangle {
int x,y;
public:
// constructor explicit cu parametri impliciti
Rectangle(int a=0, int b=0){
57
x=a; y=b; }
};
sau
class Rectangle {
int x,y;
public:
void setX(int);
void setY(int);

int aria(void) {
return x*y;
}
};

Poate clasa să arate astfel, fără să definim un constructor? DA!


Compilatorul va furniza un constructor implicit vid, care va crea
obiectul, ii va rezerva spațiu, dar nu va face inițializarea datelor
(acestea pot primi valori ulterior prin metodele setter setX() și setY()
58
Observații constructori:
 dacă în corpul clasei apare un constructor explicit (indiferent de
numărul de parametri), compilatorul nu mai generează
constructorul implicit

 de la C++1y, se poate păstra în clasă și constructorul implicit,


chiar dacă avem constructori cu parametri, printr-o declarație
de tipul NumeClasa()=default;

 În cazul în care într-o clasă avem constructor explicit, declaraţia


unui obiect poate să conţină valori de inițializare (dacă acel
constructor are parametri).

 Dacă este necesară crearea unui tablou de obiecte și clasa


prezintă constructori expliciți, atunci trebuie să existe un
constructor fără parametri sau în unele medii de programare e
acceptat un constructor explicit cu toți parametrii impliciți 59
Observații constructori:

 o clasă poate conţine un constructor explicit vid (fără


parametri), care:
-poate să nu aibă implementare (nerecomandat)
-dacă are implementare, face iniţializări ale datelor
membre cu valori implicite (recomandat)

 Momentul apelului unui constructor depinde de clasa de


memorare a obiectului:
 pentru un obiect global apelul se face la începerea
execuţiei programului
 în cazul unui obiect local apelul se face la intrarea în
execuţie a blocului respectiv
60
 Caracteristici specifice destructorilor :

destructorii nu pot avea parametri


într-o clasă putem defini doar un destructor
compilatorul apelează automat destructorul la
distrugerea obiectelor

61
class Rectangle {
int x,y;
public:
// constructor explicit vid
Rectangle(){
x=0; y=0; }
//constructor explicit cu parametri
Rectangle(int a, int b) {
x = a; y = b;
}
// destructor
~Rectangle(){
cout<< " Obiectul a fost distrus! " <<endl;
}
};

62
Observații destructori:

 Destructorii expliciţi sunt utili atunci când în constructori se


fac alocări dinamice

 simpla prezenţă a unui pointer printre datele membre nu


impune crearea unui destructor explicit:
 acesta trebuie să apară numai dacă se face o alocare dinamică
în constructor (ce implică data membră pointer)

 Momentul apelului unui destructor depinde de clasa de


memorare a obiectului:
 pentru un obiect global apelul se face la terminarea
execuţiei programului
 în cazul unui obiect local apelul se face la ieşirea din
execuţie a blocului respectiv
63
Exemplul 1:
// directive preprocesor pentru includerea bibliotecilor necesare

class Rectangle {
int x,y;
public:
//constructor explicit cu parametri impliciți
Rectangle(int a=0, int b=0) {
x = a; y = b;
}
void setX(int);
void setY(int);

int aria(void) {
return x*y;
} 64

};
void Rectangle::setX(int a) {
if (a>0) x=a;
else x = 0;
}

void Rectangle::setY(int b) {
if (b>0) y=b;
else y = 0;
}

int main (void){


Rectangle ob_r(4, 5); // declarare obiect
cout<< " Aria obiectului: " << ob_r.aria( ); // apel metoda
ob_r.setX(6); // apel metoda
ob_r.setY(9);
cout<< " Aria obiectului: " << ob_r.aria( ); // apel metoda

} 65
Exemplul 2:
// directive preprocesor pentru includerea bibliotecilor necesare
class Stiva
{
private:
int dim;
char *stack;
int next; // poz. urmatoare pe care se poate adauga in stiva

public:
Stiva( void ); // constr. explicit vid
Stiva( int ); // constr. cu param.
~Stiva( void ); // destructor
int push( char c );
int pop( char &c );
int isEmpty( void );
int isFull( void );
66
};
// constructori
Stiva :: Stiva(void) {
next = 0;
dim = 256;
stack = new char [dim];
}

Stiva :: Stiva(int dim_i) {


next = 0;
dim = dim_i;
stack = new char [dim];
}

// destructor
Stiva :: ~Stiva(void) {
delete [ ] stack;
} 67
// test stiva goala
int Stiva :: isEmpty(void) {
if (next <=0)
return 1;
else
return 0;
}

// test stiva plina


int Stiva::isFull(void) {
if(next >= dim) // e ocupata si ultima pozitie (dim-1)
return 1;
else
return 0;
}
68
// introducere in stiva
int Stiva::push(char c) {
if(isFull( ))
return 0;
stack[next++] = c;
return 1;
}

// extragere din stiva


int Stiva::pop(char &c) {
if(isEmpty( ))
return 0;
c = stack[--next];
return 1;
} 69
int main(void){
int i;
Stiva mesaj;
Stiva subiecteR1(10000);
Stiva subiecteR2(20000);
char buf[64];

strcpy(buf, " Programare obiectuala ");


for(i=0; i<strlen(buf); i++)
mesaj.push(buf[i]);

buf[0] = ‘0’;
i=0;
while(!mesaj.isEmpty( ))
mesaj.pop(buf[i++]);
cout << endl << buf << endl;
}
70
Constructorul de copiere (copy-constructor)
- Are rolul de a crea un obiect identic cu un obiect creat in
prealabil.
 Considerăm clasa Stiva:

class Stiva {
private:
int dim;
char *stack;
int next;

public:
Stiva(int dim_i);
~Stiva(void);
// …
71
};
Stiva :: Stiva(int dim_i)
{
next = 0;
dim = dim_i;
stack = new char [dim];
}

void main(void) Se foloseste constructorul


de copiere implicit, nu
{ avem inca unul explicit
Stiva ob1(10);
Stiva ob2(ob1); // copiază obiectul 1 in obiectul 2
// …
}
72
 Compilatorul generează automat un constructor de
copiere (implicit):
 acesta face copierea element cu element

 Probleme la utilizarea constructorului de copiere implicit:


 nu se face şi o copiere a elementelor stivei, ci numai o
atribuire între pointeri
 ambele obiecte vor utiliza aceeaşi zonă alocată dinamic
(cea corespunzătoare obiectului ob1)
 dacă va fi apelat destructorul pentru ob2, se va elibera
zona de memorie aferentă obiectului ob1 și o utilizare
ulterioară a obiectului ob1 va genera erori
 la distrugerea celor 2 obiecte se încearcă eliberarea de
2 ori a zonei de memorie a obiectului 1. 73
 Există şi alte situaţii când iniţializarea unui obiect cu datele
altui obiect de acelaşi tip conduce la o situaţie
asemănătoare cu cea precedentă:
 transferul unui obiect ca parametru către o funcţie
 transferul unui obiect ca rezultat al unei funcţii
 crearea unui obiect temporar

 În acest caz se recomandă utilizarea unui constructor


special numit constructor de copiere (copy constructor):

Nume_clasa(const Nume_clasa &ob);

74
class Stiva {

public:
Stiva(int dim_i=256);
~Stiva(void); Constructor de
Stiva(const Stiva &); copiere explicit

};

Stiva::Stiva(const Stiva &inStiva) {


next = inStiva.next;
dim = inStiva.dim;
stack = new char [dim];
for(int i=0; i<next; i++)
stack[i] = inStiva.stack[i];
75
}
 Exemplu de declaraţie cu iniţializare :

int main(void)
{
Stiva ob1(10);
Ambele instantieri
Stiva ob2(ob1); utilizeaza constructorul de
Stiva ob3=ob1; copiere

// …
}

76
 Constructori cu conversie de tip
Sunt constructori care au primul parametru de tipul unui
atribut al clasei și dacă există alți parametri, aceștia
trebuie să aibă valori implicite.

class Student{
private: char nume[30];
int anul;
int nr_matricol;
public: Student(const char *n, int an=1, int nr=1234);

}; Valori implicite daca vrem
sa fie constructor cu
conversie de tip 77
Student::Student(char *n, int an, int nr) {
strcpy(nume, n);
anul=an;
nr_matricol=nr;
}
void main() {
Student s= "Popescu"; // instantiere ce foloseste constructorul cu
// conversie de tip

}
Obs. Constructorul convertește un sir de caractere (char *) într-
un obiect de tip Student

78
Concluzie: în funcție de tipul argumentelor pasate
constructorilor aceștia pot fi:

1. Constructor implicit: furnizat de compilator, în cazul în


care clasa nu prezintă nici un constructor explicit
2. Constructor explicit vid (fără parametri)
3. Constructor explicit cu parametri
4. Constructor explicit cu unul sau mai mulți (chiar și cu
toți) parametri impliciți
5. Constructor de copiere
6. Constructor cu conversie de tip

79
TIPURI DE OBIECTE
 Automatice, sunt create de fiecare dată când apare o
definiție a unui obiect la execuție și sunt distruse când se
părăsește blocul unde au fost create
 Statice (globale, locale), sunt create o singură dată,
când programul e pornit (în cadrul blocului pentru cele
locale) și sunt distruse la sfârșitul programului.
 Dinamice, sunt create de programator în heap-ul
dinamic cu operatorul new și sunt distruse cu delete.
 Derivate, aparțin claselor derivate, dar înainte se
apelează constructorul clasei de bază și apoi
constructorul din clasa derivată, dacă e necesar
 Anonime, au o existență temporară, fiind necesare în
evaluarea expresiilor sau la transferul parametrilor pe
80
stivă
Alte observații legate de constructori:

 Constructorii pentru obiectele statice globale dintr-un


fișier se execută în ordinea declarațiilor, iar destructorii se
apelează în ordine inversă.
 Dacă se apelează un constructor pentru un obiect static
local, el se va apela după apelul constructorului pentru
obiectele statice globale. Argumentele pentru acești
constructori trebuie să fie expresii constante.

 Execuția lui main() nu e începutul unei aplicații C++,


obiectele statice globale cin, cout, cerr, fiind instanțiate
înainte de main(). Dacă programul se termină prin exit(),
destructorii pentru obiectele statice globale vor fi aplelați
automat. De aceea. prin apelul lui exit() într-un destructor
se poate ajunge la recursivitate infinită. Dacă programul
se termina cu abort() destructorul nu va fi apelat. 81
 Obiectele de memorie liberă se mai numesc obiecte
dinamice. Ele sunt create cu operatorul new și distruse
cu delete. C++ nu oferă garanția că un destructor este
întotdeauna apelat pentru obiectele create cu new.
Neeliminarea unui obiect nu e o eroare, doar pierdere de
memorie, dar ștergerea de două ori a aceluiași obiect
poate să ducă la situații incontrolabile.
Considerând clasa Student definită anterior, putem crea

un obiect dinamic astfel:
Student *sd=new Student("Moldovan", 1, 1023);
Obiectul va fi distrus cu delete:
delete sd;

82
EXEMPLE: CONSTRUCTORI EXPLICITI
#include <iostream>
using namespace std;
class C{
int x,y;
public: C(){
cout<<"\nCons. explicit fara param. Atributele x si y nedefinite \n";
cout <<"x = "<< x <<'\n';
cout <<"y= "<< y <<'\n'; }
C(int a){ x=a;
cout<<"\nCons. cu un param explicit; Atributul y nedefinit \n";
cout << "x = "<<x <<'\n';
cout << "y= "<<y << '\n'; }
C(int a, int b){ x=a;
y=b;
cout<<"\nCons. cu doi param. expliciti. Atribute definite \n";
cout <<"x = "<<x <<'\n'; 83
cout <<"y= "<< y <<'\n';
}
void mutator(int a, int b){ x=a;
y=b; }
void afis(){
cout<<"Afis. metoda"<<endl;
cout <<"x = "<< x <<'\n';
cout <<"y= "<< y <<'\n'; }
};//C class
int main(){
C ob1(10,20); //foloseste constructorul cu 2 parametri
ob1.mutator(7,7);
ob1.afis();
C ob2(10); // //foloseste constructorul cu 1 parametru
ob2.afis();
C ob3; //val nedefinita, constr. explicit fara param.
ob3.afis();
ob3.mutator(1,1);
ob3.afis(); cin.get();
}//main 84
85
CONSTRUCTORI PENTRU ALOCAREA DINAMICA A MEMORIEI
#include <iostream>
using namespace std;
const int MaxBufferSize = 1023;

class CFieldEdit {
private:
char* buffer;
int bufferSize;
public:
CFieldEdit(int fieldSize);
~CFieldEdit();
int getBufferSize() { return bufferSize; } //RetBufferSize
char* getBuffer() { return buffer; } //RetBuffer
}; //CFieldEdit Class

86
//begin constructor
CFieldEdit::CFieldEdit(int fieldSize) {
if (fieldSize > MaxBufferSize)
fieldSize = MaxBufferSize;
// daca lungimea introdusa > decat cea maxima prestabilita
// → se reduce la cea maxima prestabilita
buffer = new char[fieldSize];
bufferSize = fieldSize;
cout<<"\nAm construit un obiect de tip CFieldEdit";
} //CFieldEdit cons

//begin destructor
CFieldEdit::~CFieldEdit() {
delete[] buffer;
printf("\nAm distrus un obiect de tip CFieldEdit");
87
} //~CfieldEdit destructor
int main() {
CFieldEdit* aField;
char* str, c;
aField = new CFieldEdit(500);
str = aField->getBuffer(); // str va contine adresa buffer-ului

cout<<"\nAvem un buffer de "<< aField->getBufferSize()<<"


octeti";
cout<<"\nIntroduceti un sir de caractere:";
cin >> str;
cout<< "\nContinut buffer: "<<aField->getBuffer();
delete aField;
}//main

88
COPY-CONSTRUCTOR
#include <iostream>
using namespace std;
class ObiectSimplu {
public:
int valoare;
ObiectSimplu() {
valoare = 0;
}
ObiectSimplu(int v) {
valoare = v;
}
ObiectSimplu(const ObiectSimplu &o) { // copy-constructor
valoare = o.valoare;
} //nu e necesar, nu avem campuri ce necesita alocare dinamica
89
};//ObiectSimplu Class
class ObiectPointer {
public:
int * valoare;
ObiectPointer() {
valoare=new int;
*valoare = 0;
}
ObiectPointer(int v) {
valoare=new int;
*valoare = v;
}
};// ObiectPointer Class
Obs! Clasa nu are constructor de copiere explicit
90
class ObiectPointerCC {
public:
int * valoare;
ObiectPointerCC() {
valoare=new int;
*valoare = 0;
}
ObiectPointerCC(int v) {
valoare=new int;
*valoare = v;
}
ObiectPointerCC(const ObiectPointerCC & o) {
valoare=new int;
*valoare = *o.valoare;
} 91
};// ObiectPointerCC
int main() {
cout<<"Se creaza un obiect simplu a cu valoarea 2.\n";
ObiectSimplu a(2);
cout<<"Se copiaza a in b.\n";
ObiectSimplu b = a;
cout<<"Valoarea lui b este %d.\n", b.valoare;
cout<<"Se modifica valoarea lui a la 3.\n";
a.valoare = 3;
cout<<"Valoarea lui a este %d.\n", a.valoare;
cout<<"Valoarea lui b este %d.\n", b.valoare;
cout<<"\nSe creaza un obiect cu pointer c cu valoarea 10.\n";
ObiectPointer c(10);
cout<<"Se copiaza c in d.\n";
ObiectPointer d = c;
cout<<"Valoarea lui d este %d.\n", *d.valoare;
92
cout<<"Se modifica valoarea lui c la 13.\n";
*c.valoare = 13;
cout<<"Valoarea lui d este %d.\n", *d.valoare;
cout<<"Valoarea lui c este %d.\n", *c.valoare;

cout<<"\nSe creaza un obiect cu pointer, cu copy constructor, e cu val 7.\n";


ObiectPointerCC e(7);
cout<<"Se copiaza e in f.\n";
ObiectPointerCC f = e;
cout<<"Valoarea lui f este %d.\n", *f.valoare;
cout<<"Se modifica valoarea lui e la 8.\n";
*e.valoare = 8;
cout<<"Valoarea lui f este %d.\n", *f.valoare;
cout<<"Valoarea lui e este %d.\n", *e.valoare;
return 0;
}
!!! Urmariti rezultatele in cazul obiectelor din clasa ObiectPointer, respectiv
ObiectPointerCC (care are copy-constructor explicit)
93
94
INTREBARI PENTRU APROFUNDARE CUNOSTINTE
Ce este o clasa?
Ce poate contine o clasa?
Ce este un obiect?
Conceptele de baza ale POO (incapsulare, abstractizare, mostenire,
polimorfism)
Ce sunt specificatorii de acces? Care sunt acestia? Care este rolul
fiecaruia?
Cum se creeaza (instantiaza) un obiect?
Cum se poate face accesul la membrii unei clase? (prin obiect,
pointer, referinta)
Ce este pointerul this? Utilizari.
Metode accesor si mutator. Rolul acestora.
Ce este un constructor? Caracteristici.
Ce este un destructor? Caracteristici.
Ce tipuri de constructori cunoasteti? 95

Ce este un constructor de copiere? Cand este necesar?


PROGRAMAREA
CALCULATOARELOR 2

Modul 2
1
Curs 5
Argumente cu valori implicite

 Metodele membre pot avea argumente cu valori implicite,


cu respectarea aceloraşi reguli :
 aceste argumente trebuie să apară în coada listei de
parametri
 expresiile de iniţializare trebuie să poată fi convertite la
tipul argumentului

2
class Point
{
int x,y;
public :
Point(int a=0, int b=0) {
x=a; y=b;
}
};

Point p1; // echivalent cu Point(0,0);
Point p2(10); // echivalent cu Point(10,0);
Point p3(10,10);
3
Liste de iniţializare

4
 Iniţializarea datelor membre se poate face:
 în corpul constructorilor (prin atribuiri)
 în liste de iniţializare ce apar între antetul constructorului
şi corpul acestuia :

Point::Point(int a, int b) : x(a), y(b)


{
// corp constructor
}

 În acest caz iniţializarea se face înainte de execuţia


corpului constructorului

5
Referinţe ca date membre

 Declaraţia acestora se face în mod obişnuit :


class Point {
int x,y;
int &ref_a;

};

 Iniţializarea acestor referinţe se poate face însă numai


prin liste de iniţializare :

Point::Point(const int a, int b, int c) : ref_a(a)


{
x=b;
y=c;
… // corp constructor 6

}
Membri constanţi

 Datele membre pot fi declarate constante utilizând


modificatorul const astfel:

class Point {
const int x,y;

};

 Aceste date membre pot fi iniţializate numai în


constructor, printr-o listă de iniţializare
 Nici o altă metodă nu mai poate modifica aceste date
membre 7
 Metodele unei clase pot fi definite ca metode constante
pentru a specifica faptul că aceste metode pot fi accesate
de obiecte constante:

class Point {
const int x,y;
public :
Point(int a, int b) : x(a), y(b){;}
void print1(void) const;
void print2(void);
};

void Point::print1(void) const {


cout << "\n Abscisa (const) : " << x;
cout << "\n Ordonata (const) : " << y;
} 8
void Point::print2(void) {
cout << "\n Abscisa : " << x;
cout << "\n Ordonata : " << y;
}

int main(void)
{
const Point pct(5,1);
pct.print1( );
//pct.print2( );
}

 La compilare, în funcţia main( ) se va genera cel puţin un


avertisment legat de metoda print2( ) apelată pentru un
obiect constant, iar la executie o eroare:
 error C2662: 'Point::print2' : cannot convert 'this' pointer from
'const Point' to 'Point &' 9
EXEMPLU CONST CU FIȘIER ANTET
// Fișierul header hPoint.h:
class Point {
const int x,y;
public :
Point(int a, int b) : x(a), y(b){;}
void print1(void) const;
void print2(void);
};//Point class

10
// Codul sursă App_Point.cpp
#include <iostream>
using namespace std;
#include "hPoint.h"

void Point::print1(void) const {


cout << "\n Abscisa (const) : " << x;
cout << "\n Ordonata (const): " << y;
}
void Point::print2(void) {
cout << "\n Abscisa : " << x;
cout << "\n Ordonata : " << y;
}
void main(void){
const Point pct(5,1); //const Point
pct.print1( ); 11
//pct.print2( );
}
Obiecte ca date membre
 Datele membre pot fi obiecte ale unei alte clase :

class Point {
private :
int x, y;
public:
Point( ) {x=0; y=0;}
Point(int a, int b) {x=a; y=b;}
};
class Rectangle {
Point topLeft;
Point botRight;
public:
Rectangle(int, int, int, int); 12

};
 Constructorul clasei Rectangle trebuie să facă iniţializarea
obiectelor de tipul Point prin intermediul unei liste de
iniţializare:

Rectangle::Rectangle(int left, int top, int right, int bottom):


topLeft(left, top), botRight(right, bottom)
{
// corp constructor
}

13
Apelul unui constructor din alt constructor (incepand cu
C++1y)

 C++1y a adaugat facilitatea de a apela un constructor din altul,pemitand


combinarea codului in constructori. Apelul se realizeaza printr-o lista de
initializare:
//Header.h
class MyClass {

14
int x;
double y;
public:
// constructor cu 1 param
MyClass(int a, double b) {
x=a;
y = b;
}
// constructor cu 2 param
MyClass(int c): MyClass(c, 0.) {

}
int getX( ) { return x; }
double getY( ) { return y; }
};
//main
#include <iostream>
using namespace std;
#include "Header.h"

int main( ) {
int a;

15
double b;
cout << "\nEnter an int: ";
cin >> a;
cout << "\nEnter a double: ";
cin >> b;
MyClass ob1(a, b);
cout << "\nValues of ob1 are: " << ob1.getX() << " " << ob1.getY();
cout << "\nEnter other int: ";
cin >> a;
MyClass ob2(a);
cout << "\nValues of ob2 are: " << ob2.getX() << " " << ob2.getY();
}
Tablouri de obiecte

16
Tablouri de obiecte

 Pentru tipurile definite de utilizator se pot defini tablouri


similar cu definirea tablourilor pentru tipurile predefinite:
Point patrat[4];

 În acest caz avem un constructor implicit vid sau trebuie


să existe un constructor explicit fără parametri (sau cu toți
parametrii impliciți în unele medii de programare) în clasa
Point, ce va fi apelat pentru fiecare element din tablou
 De asemenea, începând cu C++1y, se poate păstra
constructorul implicit în clasă, prin declarația
NumeClasa()=default;
pentru a fi utilizat pentru crearea de tablouri de obiecte.
17
Tabloul poate fi iniţializat la declarare :
Point patrat[4] = {Point(5,5), Point(5,10), Point(10,10)};

Observații:
 pentru fiecare element din lista de iniţializare se
apelează constructorul cu parametri
 dacă lista de iniţializare nu are suficiente elemente,
pentru elementele din tablou neiniţializate se apelează
constructorul fără parametri sau cel cu toti parametrii
impliciti
 dacă la declarare se face inițializarea tuturor
elementelor utilizând constructorul cu parametri, atunci
constructorul fără parametri poate lipsi (excepție în
unele medii de programare)

18
 Dacă constructorul poate fi apelat cu un singur parametru,
se admite o formă scurtă astfel:

Point patrat[4] = {5, 10, 15, 20};


echivalentă cu:

Point patrat[4] = {Point(5), Point(10), Point(15), Point(20)};

19
 Tablourile de obiecte pot fi create dinamic astfel:
Point *patrat = new Point[4];

delete [ ] patrat;

 Obiectele unui tablou dinamic nu pot fi iniţializate explicit


la declarare, ca urmare trebuie să existe un constructor
fără parametri pentru acea clasă (sau toţi parametrii
impliciţi)
 Ulterior se pot face iniţializări explicite astfel :
patrat[0] = Point(5,5);

20
Rectangle.h

class Rectangle {
// membri privati
int height;
int width;
public:
// membri publici
Rectangle(int h=10, int w=10); // constructor explicit cu toti parametrii impliciti
//sau de la C++1y
// Rectangle(int h, int w);
// Rectangle()=default;
int get_area(void);
void set_values(int h, int w);
21
};
Rectangle::Rectangle(int h, int w) // constructor explicit
{
height = h;
width = w;
}

int Rectangle::get_area(void){
return height * width;
}

void Rectangle::set_values(int init_height, int init_width){


height = init_height;
width = init_width;
}
22
#include "Rectangle.h"
#include <iostream>
using namespace std;

int main( )
{
int i;
cout << "\n\nTablou de obiecte initializat la declarare\n";
Rectangle group1[4]={
Rectangle( ), //echivalent Rectangle(10,10),
Rectangle(20,10),
Rectangle(30,10),
Rectangle(40,10)
};
23
for (i = 0 ; i < 4 ; i++)
cout << "\tAria dreptunghiului: " << group1[i].get_area() << "\n";
cout << "\n...................................\n\n";
// tablou de obiecte
Rectangle group2[4];
cout << "\nTablou de obiecte initializat cu metoda set_values()\n";
for (i = 1 ; i < 4 ; i++)
group2[i].set_values(i + 10, 10);

for (i = 0 ; i < 4 ; i++)


cout << "\tAria dreptunghiului: " << group2[i].get_area() << "\n";
cout << "\n...................................\n\n";

24
// tablou dinamic
Rectangle *group3 = new Rectangle[4];
cout << "\nTablou dinamic de obiecte initializat cu set_values()\n";
for (i = 1 ; i < 4 ; i++)
(group3+i)->set_values(i + 10, 10);

for (i = 0 ; i < 4 ; i++)


cout << "\tAria dreptunghiului: " << group3[i].get_area() << "\n";

delete [ ]group3;
cout << "\n...................................\n\n";

25
// tablou dinamic
Rectangle *group4 = new Rectangle[4];
cout << "\nTablou de obiecte initializat prin constructor\n";
group4[0]=Rectangle(5,10);
group4[1]=Rectangle(15,20);
group4[2]=Rectangle(25,30);
group4[3]=Rectangle(35,40);

for (i = 0 ; i < 4 ; i++)


cout << "\tAria dreptunghiului: " << (group4+i)->get_area() << "\n";

delete [ ]group4;
cout << "\n...................................\n\n";
cin.get();
}//main
26
27
Membri statici

28
 Membrii unei clase, fie ei atribute(variabile) sau metode,
pot avea în declaraţia lor specificatorul static .

 Variabile statice
 Comportamentul datelor membre statice este similar cu
cel al variabilor statice obişnuite, dar primele nu sunt
accesibile decât prin intermediul clasei
 Pentru datele nestatice ale unei clase există copii
distincte în fiecare obiect (valori distincte)
 Datele statice există într-o singură copie:
 dacă un obiect modifică o variabilă de tip static, noua
valoare va fi văzută în toate obiectele instanţiate din clasa
respectivă

29
 Crearea, iniţializarea şi accesul la aceşti membri sunt
independente de celelalte obiecte ale clasei, motiv pentru
care se mai numesc şi variabile de clasă
 Membrii statici ai unei clase pot fi declaraţi în oricare
secţiune (private, public sau protected), iar funcţiile
membre au acces la membrii statici la fel ca la oricare
membru
 Un membru static poate fi referit din exterior astfel:
 indicând numele clasei şi folosind operatorul de rezoluţie “::”,
chiar dacă nu există obiecte instanţiate ale clasei
 specificând un obiect al clasei şi folosind un operator de
acces adecvat ( “.” pentru accesul prin obiect, respectiv “->”
pentru accesul prin pointer)
30
 Variabilele statice trebuie redeclarate în exteriorul clasei,
în caz contrar, compilatorul va afişa un mesaj de eroare:
 acest lucru se datorează faptului că în momentul
declarării unei variabile statice în interiorul clasei,
acesteia nu îi este alocată memorie, operaţia
efectuându-se prin redeclararea acesteia în exterior,
cu specificarea clasei de care aparţine respectiva
variabilă
 în cadrul redeclarării, nu mai apare cuvântul cheie

static

 Declaraţia externă poate fi însoţită de iniţializare (altfel


se face o iniţializare implicită cu valoarea 0)

31
class MyClass {
int x;
public:
static int n;
MyClass (int v) {
cout<<"\nApel constructor cu valoarea: "<<v<<"\n";
x = v;
n++;
}
// metoda de afisare (nerecomandat), de regula folosim getteri pentru accesul
// la date (vedeti exemplul urmator) → slide 30
void showVal(void) {
cout << "Date membre: "<< "x = " << x;
cout << ", n = " << n << "\n";
}
~MyClass( ) {
n--;
cout<<"\nApel destructor: n = "<<n<<"\n";
} 32
};
int MyClass::n; // variabila statică va fi astfel vizibilă

int main( ) {
cout<<"Acces prin numele clasei: n = "<< MyClass::n <<"\n";

MyClass a(3);
a.showVal();
cout<<"\nAcces prin obiectul a: n = "<< a.n <<"\n";

MyClass b(5);
b.showVal();
cout<<"\nAcces prin numele clasei: n = "<<MyClass::n<<"\n";
}

33
 Metode statice
 Pot exista metode precedate de specificatorul static
 Caracteristici:
 au acces doar la alţi membri de tip static ai clasei şi
bineînţeles, pot lucra cu membri globali
 nu pot avea pointeri this

 nu poate exista o versiune statică şi una nestatică a

aceleiaşi metode

 Utilizare uzuală: la definirea rutinelor callback


(programare Windows) pentru care lista de parametri nu
este sub controlul programatorului
 !!! Apel prin obiect sau referință și operatorul ”.”, prin
pointer și operatorul “->” sau prin numele clasei și
operatorul “::”. 34
//campuri si metode statice
#include <iostream>
using namespace std;

class MyClass {
int x;
static int y; //atribut privat
public:

35
void setX(int a){
x = a; }
static void setY(int b) {
y = b; }
int getX( ) {
return x; }
static int getY( ) { pentru acces la atribut privat
return y;
}
};

int MyClass::y; //variabila statica va fi astfel vizibila si initializata cu 0


int main( ) {
MyClass ob1, ob2;
ob1.setX(1);
ob1.setY(2); //nerecomandat
MyClass::setY(3);
cout << "\nValoarea lui x(nonstatic): " << ob1.getX();
cout << "\tValoarea lui y(static) prin class: " << MyClass::getY();
cout << "\nValoarea lui y(static) prin obiect: " << ob1.getY();
//nerecomandat
ob2.setX(5);
ob2.setY(6); //nerecomandat
MyClass::setY(7);
cout << "\nValoarea lui x(nonstatic): " << ob2.getX();
cout << "\tValoarea lui y(static) prin class: " << MyClass::getY();
cout << "\nValoarea lui x(nonstatic): " << ob1.getX();
cout << "\tValoarea lui y(static) prin class: " << MyClass::getY();
cin.get( );

36
}
Domeniu de vizibilitate pentru clase
Clase interioare (locale, imbricate)

37
 O clasă introduce un domeniu de vizibilitate la fel ca o
funcţie sau un bloc

 Toţi membrii clasei aparţin acestui domeniu şi ca urmare


sunt ascunse alte definiţii externe de entităţi cu acelaşi
nume

38
 Entităţile externe pot fi accesate folosind operatorul de
rezoluţie astfel:

int x;
class Point {
int x,y;
public:
Point(void);

};

Point::Point(int x) {
x=::x;
39
}
 Declaraţia unei clase poate apărea în următoarele
ipostaze :

 la nivel global, în afara oricărei clase sau funcţii; în


acest caz avem clase globale (cazul uzual);

 în domeniul unei alte clase, rezultând clase imbricate;

 la nivel local, în interiorul unui bloc; în acest caz avem


de-a face cu clase locale;

40
 Clasele imbricate se folosesc atunci când o clasă
este utilizată doar de o altă clasă:

 clasa interioară poate apărea în orice secţiune a clasei


exterioare

 clasa exterioară nu are privilegii în ceea ce priveşte


accesul la membrii clasei interioare

 clasa interioară poate fi considerată membră a clasei


exterioare, dar membrii acesteia nu sunt membri ai
clasei exterioare

 pentru a avea acces din clasa exterioară la membrii


privaţi din clasa interioară şi invers, se foloseşte
conceptul de clasă prietenă
41
class Rectangle {
public:
Rectangle(int, int, int, int);
private:
class Point {
int x,y;
public :
Point(int, int);
};
Point topLeft;
Point botRight;

}; 42
 Definiţiile metodelor clasei Point şi accesul la obiectele
clasei trebuie să conţină calificarea completă, utilizând
operatorul de scop:

Rectangle::Point::Point(int x, int y){


// corp metoda
}

Rectangle::Point pct(1,1); // instanțierea unui obiect
// din clasa interioara Punct

43
 Clasele locale se folosesc atunci când acestea sunt
folosite numai în interiorul unui bloc (funcţie sau
instrucţiune compusă):

 sunt accesibile numai în interiorul blocului


 trebuie definite complet în interiorul blocului, deci
metodele trebuie să fie de tip inline, adică foarte
simple
 În clasele locale nu putem avea date membre statice
 Declarațiile din interiorul unei clase locale pot folosi
doar nume de tipuri, enumerări, variabile statice din
domeniul exterior clasei (bloc), variabile globale, funcții
și variabile externe

44
// Membrii statici și clasele locale
#include <iostream>
using namespace std;

// clasele imbricate POT avea membri statici


class Outer {
class Inner {
static int i; // OK
};
};
int Outer::Inner::i = 47;

45
// clasele locale NU POT avea membri statici
void f() {
class Local {
public:
//! static int i; // Eroare
// Cum poate fi definit i ?
} x;

int main() {
//Outer x;
f(); 46

} // main
#include<iostream>
using namespace std;
int x; // variabile globale
void f() { // definirea funcției
static int y; // variabila statică y poate fi utilizată de clasa locală
// int x; // variabila auto x nu poate fi utilizată de către clasa locală
extern int g(); // funcția externă g poate fi utilizată de clasa locală
class Local { // clasa locală
int g() {cout<<"\nextern local"; return 1;
//return x; // error, local variable x cannot be used by g()
} //g
public: int h() { return y; } // ok, y variabilă statică din blocul extern
int k() { return ::x; } // ok, x- variabilă globală
int l() { return g(); } // ok, g() este funcție externă
}; // class Local
Local ob;
cout<< ob.l(); 47

} // f()
Funcții prietene
Clase prietene

48
 Funcţia prietenă nu este membră a clasei, dar are acces
la toţi membrii clasei în care este declarată
 Declaraţia unei funcţii prietene se face în interiorul clasei
folosind cuvântul cheie friend

 Există mai multe situaţii posibile :


 o funcţie friend independentă
 o funcţie friend unei clase este membră a altei clase
 o funcţie este prietenă mai multor clase

49
Exemplu: funcție friend independentă

class MyClass {
private: int m, n;
public:
friend float media(MyClass x);
void init(int x, int y) {
m = x;
n = y;
}
};

// are acces chiar si la membrii de tip private din clasa


float media(MyClass x)
{
float rez;
rez = (float)(x.m + x.n)/2;
50
return rez;
}
Exemplu: funcție friend membră în altă clasă
class X; // declarație incompletă

class Y {
public:
float media(X x) {
// corp metoda
}

};

class X {
private: int m, n;
public:
friend float Y::media(X x);
51
};
FUNCTII FRIEND, STRUCTURI ȘI CLASE

a) structura
struct Complex{
double re;
double im;
};
double modul (Complex *z){
return sqrt(z->re * z->re + z->im * z->im);
}
apel:
Complex z1 = {1,1};
double d = modul (&z1);
52
b) Metode membre în clase
class Complex{
double re, im;
public:
Complex(double x=0, double y=0) {
re = x;
im = y; }
double modul(){
return sqrt(re*re + im*im); }
...
}; //Complex class
apel:
Complex z1(1,1); 53

double d = z1.modul();
c) Functii friend în clase
class Complex{
double re, im;
public:
Complex(double x=0, double y=0) {
re = x;
im = y; }
friend double modul(Complex *z);
...
}; //class
double modul (Complex *z){
return sqrt(z->re*z->re + z->im*z->im);}
apel:
Complex z1(1,1); 54

double d =modul (&z1);


Observații:

 Funcţiile friend se utilizează pentru a realiza operaţii


între clase
 Utilizarea funcțiilor friend implică specificarea clară a
parametrilor de apel, pointerul this nefiind valabil.

 Utilizarea acestor funcţii prietene vine în contradicţie


cu conceptul de ascundere a datelor şi metodelor
(incapsulare)

55
Clase prietene

 Clasa prietenă are acces la toţi membrii celeilalte clase

class Square; // declarație incompletă

class Rectangle
{
int x, y;
public :
int aria(void) {
return x*y;
}
int setVal(int, int);
void convert(Square);
56
};
class Square
{
private :
int x;
public :
void setVal(int a) {
x=a;
}
friend class Rectangle; //Rectangle e friend cu Square
};

void Rectangle::convert(Square a)
{
x = a.x;
y = a.x;
} 57
 Intrebari:
- Ce este o lista de initializare? Cand trebuie folosita?
- Ce este o clasa interioara (imbricata)? Cum
instantiem obiecte dintr-o clasa interioara?
- Ce este o clasa locala? Cum poate fi utilizata?
- Ce intelegeti printr-un atribut static al unei clase?
- Este suficient sa declaram un atribut static in clasa
pentru a putea fi folosit?
- Cum poate fi accesat din afara clasei un membru
static public?
- O metoda membra statica are access la toti membrii
clasei?

58
 Intrebari
- Ce este o functie prietena? Cum se declara? Cum
poate accesa membri ai clasei pnetru care e prietena?
- O functie prietena unei clase poate fi fara parametri?
- O functie prietena unei clase poate fi declarata in
orice sectiune a clasei?
- Ce este o clasa prietena?

59
PROGRAMAREA
CALCULATOARELOR 2

Curs 6-7
1
Supraîncărcarea metodelor și
operatorilor
SUPRAÎNCĂRCAREA FUNCŢIILOR ȘI METODELOR
 În general, se consideră că metodele aparțin claselor și
funcțiile sunt exterioare claselor, în limbajul C++, deci
tratarea lor se va face ca atare.

 Supraîncărcarea funcţiilor și metodelor (overloading)


oferă posibilitatea de a scrie un set de funcţii/metode
care, deşi au nume identice, prelucrează în mod diferit
datele primite la intrare

 Pentru ca operaţia să se desfăşoare cu succes,


funcţiile/metodele respective trebuie să difere prin
semnătură, adică prin tipul datelor primite ca parametri,
ordinea lor, numărul lor
2
int myFunction(int x)
{
return x;
}

int myFunction(int x, int y)


{
return x*y;
}

3
 Compilatorul nu face distincţie între un parametru
transferat prin valoare şi unul transmis prin referinţă. În
acest caz trebuie utilizat un parametru suplimentar,
redundant (ce nu va fi folosit de funcţie):

int myFunction(int x){


return x;
}

int myFunction(int &x, int a){


return x;
}
4
 Supraîncărcarea funcţiilor nu poate fi realizată prin
diferenţierea celor două module doar prin tipul valorii
returnate
 Două declaraţii de funcţii de genul:

int myFunction(int x);


double myFunction(int x);

nu vor fi acceptate de către compilator

5
 Supraîncărcări de funcţii şi ambiguităţi
 Ambiguităţile sunt legate de conversia automată a tipului
în C++:
float myFunc(float i);
double myFunc(double i);

 Apel:
cout<< myFunc(1.9); // fara ambiguitati – double
Cout<<myFunc(1.9f); // fara ambiguitati - float
cout<< myFunc(10); // cu ambiguitati

6
 Metodele unei clase pot fi supraîncărcate la fel ca orice
funcţie
 Uzual supraîncărcarea se foloseşte în cazul
constructorilor:
 de obicei, o metodă constructor este folosită pentru a
iniţializa anumite date din cadrul clasei
 supraîncărcarea permite lucrul cu mai multe tipuri de date,
ce necesită iniţializări distincte

 Nu este supraîncărcare de metode una din situaţiile


următoare:
 două metode/funcții cu acelaşi nume, una independentă şi
alta membră a unei clase
 două metode cu acelaşi nume, fiecare fiind membră a unei
alte clase, fără legătură între acestea
! domeniile de vizibilitate nu se suprapun în aceste cazuri
7
SUPRAÎNCĂRCAREA OPERATORILOR

Noţiuni introductive
 Supraîncărcarea operatorilor presupune atribuirea de noi
sensuri operatorilor, care să extindă aria de aplicabilitate a
lor și pentru alte tipuri de date, inclusiv obiecte din clase.
 Să considerăm că avem o clasă Complex cu metode ce
realizează operațiile de bază și dorim să efectuăm operații
cu numere complexe :

Complex a, b, c, d, e;
e = mulcomplex(adcomplex(&a, &b), sccomplex(&c, &d));

8
Am putea scrie prin supraîncărcarea operatorilor pentru
obiecte de tip Complex, mai simplu, astfel:
e = (a+b)*(c-d) ;

 Prin supraîncărcare, nu se pierde nici unul din


rolurile şi funcţionalităţile iniţiale ale operatorilor

 În C++, aproape toţi operatorii pot fi supraîncărcaţi în


clasele proprii, astfel încât la folosirea lor pe instanţele
acestor clase, rolul jucat să fie cel stabilit de utilizator şi
nu cel predefinit

9
 Nu pot fi supraîncărcaţi operatorii:
 . (punct)
 :: (rezoluţie)
 ?: (condiţional)
 .* (indirectare) (operator doar in C++)
 sizeof
 Operatori ce pot fi supraîncărcaţi:

+ - * / = < > += -= *= /= << >> <<= >>=


== != <= >= ++ -- % & ^ ! | ~ &= ^= |=
&& || %= [] () , ->* ->
new delete new[ ] delete[ ]
Obs:
.* si ->* operatori pointer la membru - accesul prin obiect, respectiv pointer la
membru al clasei – access prin pointer (C++) 10
 Caracteristici:
 prin supraîncărcare nu se pot introduce operatori noi
 pentru tipurile predefinite nu se admit supraîncărcări
 prin supraîncărcarea operatorilor nu se poate schimba:
 n-aritatea

 prioritatea

 asociativitatea

 operatorii compuşi (+=, -=, …) nu sunt afectaţi de


supraîncărcarea operatorilor de bază (+, -, …):
 pentru aceştia trebuie făcută o supraîncărcare explicită

 dacă un operator poate fi utilizat atât ca operator unar


cât şi binar (&, *, +, -) atunci trebuie făcute
supraîncărcări diferite pentru cele două ipostaze 11
 Operatorii se supraîncarcă prin crearea unor
metode/funcții speciale, ce conţin în nume cuvântul cheie
operator (metodă/funcţie operator)

 O metodă operator defineşte operaţiile specifice pe care


le va efectua operatorul respectiv, relativ la clasa în care a
fost destinat să lucreze

 Metodele/Funcţiile operator pot fi:


 metode membre nestatice ale clasei în care vor opera
 funcţii externe, de obicei funcţii friend:
 cel puţin un argument de tipul clasei sau referinţă la

tipul clasei
12
 Operatorii ->, =, [ ] şi ( ) și cei compuși pot fi
supraîncărcaţi numai cu metode membre

 Operatorii >> (extracție) și << (inserție) se supraîncarcă


doar cu funcții friend.

 Metodele/funcţiile operator nu admit parametri cu valori


implicite

 Cu excepţia operatorului de atribuire (=) (asignare), prin


derivare se moştenesc operatorii supraîncărcaţi în clasa
de bază

 Operatorii supraîncărcaţi în clasa derivată ascund alte


supraîncărcări ale aceloraşi operatori din clasele de bază
13
Supraîncărcarea operatorilor cu metode membre

 Numărul de argumente este egal cu numărul de operanzi


minus unu:
 primul operand fiind referit prin pointerul this în mod
implicit
 operatorii unari nu au argumente
 operatorii binari au un singur argument

 Forma generală:
tip_returnat Nume_clasa::operator#(lista_argumente)
{
// operatii
}
 semnul # este o rezervare de loc pentru operatorul care
urmează să fie supraîncărcat 14
class Complex {
float re, im;
public:
Complex(float x=0, float y=0) {
re = x;
im = y;
}

void showVal(void) {
cout << "Date membre: "<< “re = " << re;
cout << ", im = " << im << "\n";
}

// supraincarcare cu metode membre


Complex operator+(Complex x);
Complex operator-(Complex x); 15
};
Complex Complex :: operator+(Complex x) {
Complex rez;
rez.im = im + x.im;
rez.re = re + x.re;
return rez;
}

Complex Complex :: operator-(Complex x) {


Complex rez;
rez.im = im - x.im;
rez.re = re – x.re;
return rez;
}
16
int main( )
{
Complex unu(5,3), doi(-1,6);
Complex trei;

trei = doi + unu;


// echivalent cu apelul explicit: doi.operator+(unu)
trei.showVal( );

trei = doi - unu;


// echivalent cu apelul explicit: doi.operator-(unu)
trei.showVal( );
}
17
EXEMPLU ŞI CU REFERINŢE:
#include <iostream>
using namespace std;
class Complex { float re, im;
public: Complex(float x=0, float y=0) {
re = x;
im = y;}
void showVal(void) {
cout << "Date membre: "<< "re = " << re;
cout << ", im = " << im << "\n"; }
// supraincarcare cu metode membre
//Complex operator+(Complex x);
Complex operator-(Complex x);
18
Complex& operator+(Complex &);
};//Complex class
/*Complex Complex:: operator+(Complex x) {
Complex rez;
rez.im = im + x.im;
rez.re = re + x.re;
return rez; }
*/
Complex Complex:: operator-(Complex x) {
Complex rez;
rez.im = im - x.im;
rez.re = re - x.re;
return rez; }

Complex& Complex:: operator+(Complex &x) {


Complex &rez= *new Complex(re + x.re, im + x.im);
19
return rez;
}
int main( ){ Complex unu(5,3), doi(-1,6), trei;
cout<< "\nPrimul numar - ";
unu.showVal();
cout<< "\nAl doilea numar - ";
doi.showVal();
/*cout<< "\n Suma (doi + unu) ";
trei = doi + unu;
trei.showVal( );*/
// echivalent cu apelul explicit: Complex &ref_trei=doi.operator+(unu)
Complex &ref_trei=doi + unu;
cout<< "\n Suma (doi + unu) ";
ref_trei.showVal( );
// echivalent cu apelul explicit: doi.operator+(unu)
cout<< "\n Diferenta - (doi - unu) ";
trei = doi - unu; // echivalent cu apelul explicit: doi.operator-(unu)
20
trei.showVal( );
}
Supraîncărcarea operatorilor prin funcţii friend

 În acest caz este nevoie de trimiterea explicită a tuturor


operanzilor necesari deoarece funcţiile friend nu fac parte
din clasă şi deci nu au acces la pointer-ul this:
 operatorii unari au un parametru
 operatorii binari au doi parametri

 Forma generală:
friend tip_returnat operator#(lista_argumente) ;

 semnul # este o rezervare de loc pentru operatorul care


urmează să fie supraîncărcat
21
class Complex{
float re, im;
public:
Complex(float x=0, float y=0) {
re = x;
im = y;
}

void showVal(void) {
cout << "Date membre: "<< “re = " << re;
cout << ", im = " << im << "\n";
}

friend Complex operator+(Complex x, Complex y);


friend Complex operator-(Complex x, Complex y);
}; 22
Complex operator+(Complex x, Complex y)
{
Complex rez;
rez.re = x.re + y.re;
rez.im = x.im + y.im;
return rez;
}

Complex operator-(Complex x, Complex y)


{
Complex rez;
rez.re = x.re - y.re;
rez.im = x.im - y.im;
return rez;
}
23
int main( )
{
Complex unu(5,3), doi(-1,6);
Complex trei;

trei = doi + unu;


// echivalent cu apelul explicit: operator+(doi, unu)
trei.showVal( );

trei = doi - unu;


// echivalent cu apelul explicit: operator-(doi, unu)
trei.showVal( );
}
24
EXEMPLU ŞI CU REFERINŢE:
#include <iostream>
using namespace std;
class Complex { float re, im;
public:
Complex(float x=0, float y=0) {
re = x;
im = y;
}
void showVal(void) {
cout << "Date membre: "<< " re = " << re;
cout << ", im = " << im << "\n";
}
friend Complex operator+(Complex x, Complex y);
friend Complex operator-(Complex x, Complex y);
25
//friend Complex& operator+(Complex &, Complex &);
};//Complex class
Complex operator+(Complex x, Complex y) {
Complex rez;
rez.re = x.re + y.re;
rez.im = x.im + y.im;
return rez; }

Complex operator-(Complex x, Complex y) {


Complex rez;
rez.re = x.re - y.re;
rez.im = x.im - y.im;
return rez;
}
/*Complex& operator+(Complex &x, Complex &y){
Complex *t;
t= new Complex(x.re+y.re, x.im+y.im);
26
return *t;
}*/
int main( ){
Complex unu(5,3), doi(-1,6);
Complex trei;
cout<< "\nPrimul numar - ";
unu.showVal();
cout<< "\nAl doilea numar - ";
doi.showVal();
trei = doi + unu;
// echivalent cu apelul explicit: operator+(doi, unu)
cout<< "\n Suma - (doi + unu) ";
trei.showVal( );
trei = doi - unu; // echivalent cu apelul explicit: operator-(doi, unu)
cout<< "\n Diferenta - (doi - unu) ";
trei.showVal( );
} 27
 Alegerea variantei de supraîncărcare – se ține cont de n-
aritatea operatorului, de considerente de protecţie şi de
modificare a valorii operanzilor:

 operatorii unari - de regulă, implementaţi cu metode membre


 operatorii binari ce modifică valoarea unui operand -
supraîncărcaţi cu metode membre
 dacă primul operand nu este obiect din clasa pentru care se
face supraîncărcarea - funcții operator globale (de regulă
friend)
 operatorii ce nu modifică operanzii, dar generează valori noi,
pot fi supraîncărcaţi cu funcţii globale:
 de tip friend, caz în care are acces la toate datele private ale clasei
 funcţie independentă care accesează datele prin metode de interfaţă

28
Expresie Operator Metoda membră Funcţie globală
@A + - * & ! ~ ++ -- A::operator@() operator@(A)
A@ ++ -- A::operator@(int) operator@(A,int)
+ - * / % ^ & | < >
A@B == != <= >= << >> A::operator@ (B) operator@(A,B)
&& || ,
= += -= *= /= %= ^=
A@B A::operator@ (B) -
&= |= <<= >>= []
A(B,C...) () A::operator( ) (B, C...) -
A->x -> A::operator->( ) -

 Obs: Operatorii increment/decrement ( ++/-- ) pot fi


prefixați și postfixați. În cazul postfixat se adaugă un
parametru redundant de tip int, pentru a distinge
semnăturile
29
CONVERSII DE TIP

 Compilatoarele C/C++ execută în mod automat conversii


pentru datele de tipuri diferite (regula conversiilor
implicite). Aceste conversii pot apărea la :

a) aplicarea unui operator între operanzi de tipuri diferite

b) parametrul efectiv al unei funcții este diferit ca tip de


parametrul formal corespunzător

c) tipul de retur din antetul unei funcții este diferit de cel


al expresiei din instrucțiunea return.

30
 Utilizatorul poate în anumite cazuri să impună realizarea
unor conversii cu operatorul cast. El poate fi utilizat în
C++ astfel:
(tip_cast) expresie
sau
tip_cast (expresie)

 Dacă se dorește realizarea unei operații între obiecte


dintr-o anumită clasă și operanzi de alte tipuri, trebuie
definite explicit regulile de conversie care se vor aplica

31
 De exemplu: să presupunem că dorim să putem aduna 2
obiecte de tip Complex, dar și un obiect de tip Complex
cu un număr întreg

class Complex {

public:
friend Complex operator+(Complex , Complex );
friend Complex operator+(Complex , int);
friend Complex operator+(int, Complex);
};

 Obs: s-au prevăzut alte două funcţii operator


(supraîncărcare de funcţii) pentru adunarea cu un întreg
32
Complex operator+(int a, Complex c)
{
Complex rez;
rez.re=a+c.re;
rez.im=c.im;
return rez;
}

Complex operator+(Complex c, int a)


{
Complex rez;
rez.re=c.re+a;
rez.im=c.im;
return rez;
}
33
Variante de conversii
1. Se poate utiliza un constructor pentru a converti obiectul
implicat la tipul clasei în care avem supraîncărcarea
(constructor de conversie de tip):

class Complex {

public:
Complex(int x) {
re =x; im = 0;
}
friend Complex operator+( Complex c1, Complex c2);
};
34
 Ca urmare este suficientă o singură funcţie operator şi
sunt posibile expresii care implică operanzi de tipul
Complex şi operanzi întregi :

Complex z(5,6), q, v;
q= z + 5; // echivalenta cu: q = operator+(z, Complex(5));
v=3+z; // echivalenta cu: v = operator+(Complex(3),z);

35
2. Altă variantă - utilizarea unei funcţii de conversie:
operator nume_tip ( ) {
// corp metoda
}
 O astfel de funcţie va converti un obiect al clasei către
nume_tip în cursul unei conversii explicite (cast).

36
class Point {
private:
int x, y;
public:
Point (int x, int y) { Point::x = x; Point::y = y; }
Point (int x) { Point::x = Point::y = x; } // constructor de conversie de tip

friend Point operator+ (Point p, Point q) ;


friend Point operator- (Point &p, Point &q);
void print (void) { cout<<"("<<x<<","<<y<<")"; }
};

Point operator+ (Point p, Point q) {


{
return Point(p.x + q.x, p.y + q.y);
}
Point operator- (Point &p, Point &q) {
return Point(p.x - q.x, p.y - q.y);
37
}
class Rectangle {
private:
Point topLeft;
Point botRight;
public:
Rectangle(int left, int top, int right, int bottom);
Rectangle(Point &p, Point &q) : topLeft(p), botRight(q) { }
Rectangle(Point &p) : topLeft(p), botRight(p) { }

operator Point( ) { // functie de conversie


return botRight - topLeft;
}
friend Rectangle operator+(Rectangle &r, Rectangle &t);
friend Rectangle operator-(Rectangle &r, Rectangle &t);

void print (void) {


topLeft.print();
botRight.print( );
38
}
};
Rectangle operator + (Rectangle &r, Rectangle &t) {
return Rectangle(r.topLeft + t.topLeft, r.botRight + t.botRight);
}
Rectangle operator - (Rectangle &r, Rectangle &t) {
return Rectangle(r.topLeft - t.topLeft, r.botRight - t.botRight);
}
Rectangle::Rectangle (int left, int top, int right, int bottom)
: topLeft(left,top), botRight(right,bottom)
{}

int main ( ){
Point p(10,10), q(20,20);
(p+q).print( );
cout<<'\n';
(p + 10).print( ); // echivalent cu operator+(p, Point(10))
cout<<'\n';
Rectangle r(10,10,20,30);
q =r + p; // echivalent cu operator+((Point)r, p) 39
q.print( );
}
Concluzie: utilizarea obiectelor duce la situații în care
sunt necesare conversii. Aceste conversii nu pot fi
aplicate automat, spre deosebire de cazul tipurilor
predefinite.

Utilizatorul poate defini următoarele tipuri de conversie:


a) din tip predefinit -> în tip abstract
b) din tip abstract -> în tip predefinit
c) din tip abstract -> în tip abstract

40
a) Conversia din tip predefinit -> în tip abstract
Se realizează în mod automat prin apelul unui
constructor de conversie adecvat. Constructorul va avea
ca parametru tipul predefinit și eventual și alti parametri
care trebuie să fie toți impliciți.

b) Conversia din tip abstract -> în tip predefinit


Se realizează prin supraîncărcarea operatorului cast cu o
funcție membră de forma:
Nume_clasa:: operator nume_tip_predefinit ( ) {
// corp metoda
}
Metoda nu are niciun parametru, asta înseamnă că
operatorul cast este un operator unar.
Ea se apelează pentru un obiect de tip Nume_clasa și
returnează o valoare de tip nume_tip_predefinit.
41
Se utilizează asemănător cu tipurile predefinite :
(nume_tip_predefinit) obiect
sau
nume_tip_predefinit (obiect)

c) Conversia din tip abstract -> în tip abstract


 Această conversie se poate realiza cu ajutorul
constructorilor de conversie, cât și prin supraîncărcarea
operatorului cast.
 Dacă se folosește un constructor de conversie:
presupunem că avem tip1 și tip2 două tipuri abstracte,
pentru a converti un obiect de tip1 în obiect tip2, se
definește un constructor pentru clasa tip2, care să aibă
ca și parametru un obiect de tip1 de forma:
tip2(tip1 obiect);
42
Supraîncărcarea operatorului de atribuire (=)
(asignare)

 Pentru atribuirile între obiecte de acelaşi tip, se utilizează


o variantă supraîncărcată implicită a operatorului de
atribuire

 Această variantă asigură copierea datelor membru cu


membru

 În cazul în care constructorii realizează alocări dinamice,


aceste variante implicite nu sunt satisfăcătoare şi este
necesară furnizarea unei variante explicite de
supraîncărcare
43
Forma generală a metodei de supraîncărcare a operatorului
de asignare este :

Tip& Tip::operator=(const Tip &sursa)


{
if(this != &sursa)
{
// copiere date aferente obiectului din membrul drept
}
return *this;
}

 Funcţia operator trebuie să fie o metodă membră (nu poate


fi funcţie externă)
44
class Matrix
{
private:
int rows;
int cols;
int *elems;
public:
Matrix (int rows, int cols);
~Matrix (void) {
delete [ ] elems;
}
Matrix (const Matrix&); //copy constructor
Matrix& operator = (const Matrix &m);
}; 45
Matrix::Matrix (const int r, const int c) : rows(r), cols(c)
{ // rows=r; cols=c;
elems = new int[rows * cols];
}

Matrix::Matrix (const Matrix &m)


{
int n;
rows = m.rows;
cols = m.cols;
n = m.rows * m.cols;
elems = new int[n];
for (int i = 0; i < n; ++i)
elems[i] = m.elems[i];
} 46
Matrix& Matrix::operator = (const Matrix &m)
{
if (this != &m)
{
if (rows == m.rows && cols == m.cols)
{
int n = rows * cols;
for (int i = 0; i < n; ++i)
elems[i] = m.elems[i];
}
}
return *this;
}
47
 Trebuie făcută distincţia între atribuire (asignare) şi
iniţializare, pentru ambele operaţii folosindu-se simbolul =
 când simbolul = apare într-o declaraţie cu iniţializare,
avem iniţializare, care se face cu ajutorul
constructorului de copiere:

Tip ob2(ob1); // ob2 nu exista inaintea acestei
// linii de cod si va fi initializat cu ob1
Tip ob2=ob1;

 când simbolul = apare într-o expresie în care ambele


obiecte sunt deja create, avem de-a face cu o atribuire
pentru care se foloseşte varianta supraîncărcată a
operatorului de atribuire:
Tip ob1, ob2; // definire obiecte

ob2 = ob1; // asignare
48
class String {
char *p;
int size;
public:
String(char *str);
String(void);
String(const String &obj); // constructor de copiere
~String(void) {
delete [ ] p;
}

// supraîncărcarea operatorului de atribuire


String operator=(String &obj);
String operator=(char *s);
49
// supraincarcarea operatorului + (concatenare)
// String operator+(String &obj); // cu metoda membra
friend String operator+(String &obj1, String &obj2);
String operator+(char *s);
friend String operator+(char *s, String &obj);

// supraincarcarea operatorului - (substractie)


// String operator-(String &obj); // cu metoda membra
friend String operator-(String &obj1, String & obj2);
String operator-(char *s);

50
// supraincarcarea operatorilor relationali: compara obiectul curent cu un alt obiect

int operator==(String &obj)


{ return !strcmp(p, obj.p); }
int operator!=(String &obj)
{ return strcmp(p, obj.p); }
int operator<(String &obj)
{ return strcmp(p, obj.p) < 0; }
int operator>(String &obj)
{ return strcmp(p, obj.p) > 0; }
int operator<=(String &obj)
{ return strcmp(p, obj.p) <= 0; }
int operator>=(String &obj)
{ return strcmp(p, obj.p) >= 0; }
51
// supraincarcarea operatorilor relationali: compara obiectul curent cu
un sir

int operator==(char *s)


{ return !strcmp(p, s); }
int operator!=(char *s)
{ return strcmp(p, s); }
int operator<(char *s)
{ return strcmp(p, s) < 0; }
int operator>(char *s)
{ return strcmp(p, s) > 0; }
int operator<=(char *s)
{ return strcmp(p, s) <= 0; }
int operator>=(char *s)
{ return strcmp(p, s) >= 0; }

52
int strsize(void)
{ return strlen(p); }
char* makestr(char *s)
{ return strcpy(p, s); }
operator char *(void)
{ return p; }
}; // clasa String

String& String::operator = (String &x){


if( this == &x)
return *this;
if( p != NULL)
delete [] p;
p = new char[strlen(x.p)+1];
strcpy(p,x.p);
return *this; 53
}
String operator + (String &a,String &b){
char buf[200];
strcpy(buf,a.p);
strcat(buf,b.p);
return String(buf);
}

String operator - (String &a, String &b){


char *pp;
pp = strstr(a.p, b.p); // adresa unde începe sirul ob.b in sirul ob.a
if(pp == NULL) // sirul nu se gaseste in ob.a
return String(a.p);
else {
char buf[200];
strncpy(buf, a.p, pp-a.p);
strcpy(buf+(pp-a.p),pp+strlen(b.p));
return String(buf);
} 54

}
Supraîncărcarea operatorului de indexare [ ]

 Supraîncărcarea acestui operator se face pentru acele


tipuri de date ce au un comportament similar cu tablourile
 Un exemplu în acest sens este tabloul asociativ, în care
indecşii pot fi instanţe ale oricărui tip ordonat
 Prin supraîncărcarea acestui operator se urmăreşte
obţinerea unui comportament de felul următor:
a[b]  a.operator[ ](b)
Dacă se face supraîncărcarea operatorului de indexare
pentru un anumit tip, atunci tablourile de acest tip nu mai
pot fi iniţializate prin construcţii de forma:
Tip nume_tablou[ ] = {val1, val2,…, valn};
ci numai prin intermediul constructorilor
Supraîncărcarea acestui operator se poate realiza numai cu
metode membre. 55
 Exemplul 1: Dicţionar
struct assoc {
char *cuvant;
char *definitie;
};

class Dictionar {
assoc *dict;
int dim; // dimensiunea dictionarului
int next; // urmatoarea pozitie libera in dictionar

public:
Dictionar(int size) {
dim = size;
next = -1;
dict = new assoc[size];
}

int addWord(char *w, char *def); 56


char *operator[ ](const char *);
};
int Dictionar::addWord(char *word, char *def)
{
if(next <dim) {
next++;
dict[next].cuvant = new char[strlen(word)+1];
strcpy(dict[next].cuvant, word);
dict[next].definitie = new char[strlen(def)+1];
strcpy(dict[next].definitie, def);
return 0;
}
return 1;
}

57
char * Dictionar::operator [ ](const char *pCuv) {
assoc *pAssoc;
for(pAssoc = &dict[next-1]; dict <= pAssoc; pAssoc--)
if(strcmp(pCuv, pAssoc->cuvant) == 0)
return pAssoc->definitie;
return 0;
}

int main( ) {
int size = 10;
Dictionar dict(size);
dict.addWord("Masina", "Definitia 1");
dict.addWord("Copac", "Definitia 2");
dict.addWord("Animal", "Definitia 3");
dict.addWord("Student", "Definitia 4");
cout << dict["Animal"]<<endl;
}
58
Exemplul 2: clasa Analize

class Analize;
class Pers
{
char nume[20];
double greutate;
int varsta;
friend Analize;
public:
void tip();
};// Pers

59
class Analize {
Pers *sir;
int n;
public:
Analize()
{
n=5;
sir= new Pers[5];
}
Analize(int nr)
{
n=nr;
sir= new Pers[n];
}
//Supraincarcare []
Pers* operator[](char *);
Pers* operator[](double);
Pers* operator[](int);
void introdu(); // functie pentru citire date persoane– trebuie definita 60

};//Analize
//Indexare dupa nume
Pers* Analize:: operator[](char *nume) {
for(int i=0;i<n;i++)
if(strcmp(sir[i].nume,nume)==0)return &sir[i];
return NULL;
}//op[]nume

//Indexare dupa greutate


Pers* Analize:: operator[](double gr) {
for(int i=0;i<n;i++)
if(sir[i].greutate==gr)return &sir[i];
return NULL;
}//op[]greutate

//Indexare dupa index


Pers* Analize:: operator[](int index) {
if((index >=1) && (index <=n))return &sir[index-1];
else {cout << "\nIndex gresit";return NULL;}
}//op[]index 61
int main()
{
int nr=10;
double g;
char *n;
int i;
// g, n si i vor fi initializate

Analize t(nr); //creare obiect de tip Analize


t.introdu(); // citirea datelor
t[g]->tip(); // afisare persoana cu greutatea g
t[n]->tip(); // afisare persoana cu numele n
t[i]->tip(); // afisare persoana cu indicele i in tablou
}

62
Exemplul 3: BitVec
#include <iostream>
using namespace std;
typedef unsigned char uchar;
class BitVec {
uchar *vec;
short bytes;
public:
BitVec (const short dim);
BitVec (const char* bits);
BitVec (const BitVec&);
~BitVec (void) { delete [ ] vec; }
BitVec& operator = (const BitVec&);
int operator [ ] (const short idx);
void print (void); 63
};//BitVec class
BitVec::BitVec (const char *bits){ // 1011011101111
int len = strlen(bits);
bytes = len / 8 + (len % 8 == 0 ? 0 : 1); Ex: len=13 -> bytes=2
vec = new uchar[bytes]; vec[1] | (1<< 4) ->00010000
vec[1] | (1<< 2) ->00010000 | 000001
for (int i = 0; i < bytes; ++i)
00010100
vec[i] = 0; vec[1] | (1<< 1) -> 00010100 | 000000
for (int i = len - 1; i >= 0; --i) 00010110
if (*bits++ == '1') vec[i/8] |= (1 << (i%8)); }
BitVec::BitVec (const short dim){
bytes = dim / 8 + (dim % 8 == 0 ? 0 : 1);
vec = new uchar[bytes];
for (int i = 0; i < bytes; ++i)
vec[i] = 0; }
BitVec::BitVec (const BitVec &v){ bytes = v.bytes;
vec = new uchar[bytes];
64
for (int i = 0; i < bytes; ++i)
vec[i] = v.vec[i]; }
BitVec& BitVec::operator= (const BitVec &v){
int i;
if (this != &v) {
for (i = 0; i < (v.bytes < bytes ? v.bytes : bytes); ++i)
vec[i] = v.vec[i];
for (; i < bytes; ++i)
vec[i] = 0; Exemplu: idx=18
} vec[2] & (1 << 2) ? 1 : 0
return *this; -> se returneaza al 3-lea bit (de
la dreapta) din al 3-lea octet
}
inline int BitVec::operator [ ] (const short idx){
return vec[idx/8] & (1 << idx%8) ? 1 : 0; }
void BitVec::print(void){
cout <<"\nOctetii sunt in hexa: "<<endl;
for(int i=0; i<bytes; i++)
65
cout <<"\nOctet: "<< (i+1)<< hex <<": "<< (int)vec[i] << endl; }
int main (void){
BitVec v("1011011101111");
v.print( );
cout << endl << "\nBitii sunt: " << dec << endl;
for(int i=0; i< 16; i++)
cout << "Bit " << (i+1) << ": " << v[i] << endl;
BitVec vv(16);
cout<<"\n Noul vector initial este:";
vv.print();
vv=v;
cout<<"\n Noul vector dupa asignare este:";
vv.print();
cout << endl << "\nBitii sunt: " << dec << endl;
for(int i=0; i< 16; i++)
cout << "Bit " << (i+1) << ": " << vv[i] << endl;
66
}//main
Supraîncărcarea operatorului apel de funcţie ( ) -
iteratori

 Supraîncărcarea acestui operator se foloseşte pentru


obţinerea unui iterator:
 acesta permite parcurgerea secvenţială a unei colecţii
 De obicei, se creează o clasă iterator asociată cu clasa
colecţiei pe care iteratorul o va parcurge
 Prin supraîncărcare se urmăreşte obţinerea unui
comportament de felul următor:
a(b,…)  a.operator( )(b,…);
Supraîncărcarea acestui operator se poate realiza numai cu
metode membre.
67
 Matrice
class Matrix {
private:
const short rows;
const short cols;
int *elems;
public:
Matrix(const short rows, const short cols);
Matrix(const Matrix&);
~Matrix (void) {delete [ ] elems;}
int& operator( ) (const short row, const short col);
Matrix& operator=(const Matrix&);

const short getRows (void) { return rows; }


const short getCols (void) { return cols; }
}; 68
Matrix::Matrix (const short r, const short c) : rows(r), cols(c)
{
if (rows > 0 && cols > 0)
elems = new int[rows * cols];
}

Matrix::Matrix (const Matrix &m) : rows(m.rows), cols(m.cols)


{
int n = rows * cols;
if (rows > 0 && cols > 0)
elems = new int[n];
for (register i = 0; i < n; i++)
elems[i] = m.elems[i];
}

69
int& Matrix::operator( ) (const short r, const short c) {
if((r >= 0 && r< rows)&&(c >= 0 && c < cols))
return elems[r *cols + c];
else{
cout<<"\nIndici eronati! Oprire program! \n";
exit(0);
}
}

Matrix& Matrix::operator= (const Matrix &m){


if (this != &m) {
if (rows == m.rows && cols == m.cols) {
int n = rows * cols;
for (register i = 0; i < n; i++)
elems[i] = m.elems[i];
}
}
return *this; 70
}
int main (void)
{
int i, j;
Matrix m(2,3), p(2,3);
m(0,0) = 10; m(0,1) = 20; m(0,2) = 30;
m(1,0) = 15; m(1,1) = 25; m(1,2) = 35;

for(i=0; i<2; i++) {


cout<<endl;
for(j=0; j<3; j++)
cout<<m(i, j)<<"\t";
}

Matrix n=m;
cout<<endl;
for(i=0; i<2; i++) {
cout<<endl;
for(j=0; j<3; j++)
cout<<n(i, j)<<"\t";
71
}
}
SUPRAÎNCĂRCAREA OPERATORILOR DE INCREMENTARE/
DECREMENTARE (++/ --)
 Operatori unari, pot fi prefixati și postfixati.
 În cazul postfixat se adaugă un parametru redundant de tip int,
pentru a distinge semnăturile
 Operațiile se fac în obiectul curent, care va fi returnat de funcția
operator

Operator prefixat supraîncărcat în clasa A cu metode membre


A::operator++(), respectiv A::operator--()
Operator postfixat supraîncărcat în clasa A cu metode membre
A::operator++(int), respectiv A::operator--(int)

Operator prefixat supraîncărcat cu funcție friend


operator++(A), respectiv operator--(A)
Operator postfixat supraîncărcat cu funcție friend 72
operator++(A, int), respectiv operator--(A, int)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class Time{
private:
int hh; // ora
int mm; // minut
public:
Time(int m) {
if (m >= 0) {
hh = m / 60; // nr ore
mm = m % 60; // nr minute
}
else {
hh = mm = 0;
}
}
73
Time(int h = 0, int m = 0) {
if (((h >= 0) && (h < 24)) && ((m >= 0) && (m < 60))){
hh = h;
mm = m;
}
else { hh = mm = 0; }
} //Time

int getH(){
return hh;
}

void setH(int h) {
if ((h >= 0) && (h < 24))
hh = h;
else
hh = 0; }

int getM(){ 74
return mm;
}
void setM(int m) {
if ((m >= 0) && (m < 60))
mm = m;
else
mm = 0;
}

void show() {
cout << hh << ":" << mm << endl;
}
// supraincarcare operator de incrementare prefixata
Time& operator++() {
mm++;
if (mm == 60) {
hh++;
if (hh == 24)
hh = 0;
mm = 0;
}
75
return *this;
}
// supraincarcare operator de incrementare postfixata
Time operator++(int) {
Time temp = *this;
++(*this);
return temp;
}
// supraincarcare operator de decrementare prefixata
Time& operator--() {
if (mm == 0) {
hh--;
if (hh < 0) hh = 23;
mm = 59;
}
else
mm--;
return *this;
76
}
// supraincarcare operator de decrementare postfixata
Time operator--(int) {
Time temp = *this;
--(*this);
return temp;
}

// supraincarcare operator de atribuire compusa +=


Time& operator+=(int min) {
int h1, m1;
h1 = min / 60;
m1 = min % 60;
hh = (hh + h1 + ((mm+m1) / 60)) % 24;
mm = (mm + m1) % 60;
return *this;
}

77
// supraincarcare operator de atribuire compusa -=
Time& operator-=(int min) {
int h1, m1;
h1 = min / 60;
m1 = min % 60;
hh = hh - h1;
if (mm < m1) hh--;
if (hh < 0) hh = 24 + hh;
mm = mm - m1;
if (mm < 0) mm = 60 + mm;
return *this;
}
// supraincarcare operator de adunare +
Time operator+(Time tm) {
Time rez;
int nm = mm + tm.mm;
rez.hh = hh + tm.hh + nm / 60;
if (rez.hh > 23) rez.hh = rez.hh - 24;
rez.mm = nm % 60;
78
return rez;
}
// supraincarcare operator de scadere-
Time operator-(Time tm){
Time rez;
if (mm < tm.mm)
{
rez.hh = hh - tm.hh - 1;
rez.mm = 60 - (tm.mm - mm);
}
else
{
rez.hh = hh - tm.hh;
rez.mm = mm - tm.mm;
}
if (rez.hh < 0)
rez.hh = 24 + rez.hh;
return rez;
}
};
79
int main(){
Time tm1(1, 29);
cout << "Time 1: ";
tm1.show();
cout << endl;
Time tm2;
tm2 = --tm1;
cout << "Time 1 (tm2 = --tm1): ";
tm1.show();
cout << "Time 2 (tm2 = --tm1): ";
tm2.show();
cout << endl;
tm1 = ++tm2;
cout << "Time 1: (tm1 = ++tm2)";
tm1.show();
cout << "Time 2: (tm1 = ++tm2)";
tm2.show();

80
cout << endl;
tm1 = tm2++;
cout << "Time 1 (tm1 = tm2++): ";
tm1.show();
cout << "Time 2 (tm1 = tm2++): ";
tm2.show();
tm2--;
cout << "Time 2 (tm2--): ";
tm2.show();

cout << endl;


Time tm3(22, 15);
cout << "Time 3: ";
tm3.show();

81
cout << endl;
tm3 += 75;
cout << "Time 3 (+=75): ";
tm3.show();
tm3 -= 86;
cout << "Time 3 (-=86): ";
tm3.show();

Time tm4(23, 18);


cout << endl;
cout << "Time 4: ";
tm4.show();
cout << "Time 3 + Time 4 : ";
(tm3+tm4).show();
cout << "Time 3 - Time 4 : ";
(tm3 - tm4).show();

}
82
Supraîncărcarea operatorilor new şi delete

 Scop: de a efectua anumite operaţii speciale de


alocare/eliberare de memorie în momentul creării sau
distrugerii unui obiect din clasa în care aceştia au fost
supraîncărcaţi

 Operatorii new şi delete pot fi supraîncărcaţi numai cu


metode membre statice (nu se precizează în mod explicit)

83
 Sintaxa generală de supraîncărcare locală (sau utilizator) a
acestor operatori cu metode statice:

void *operator new(size_t dim)


{
… // operatii de alocare de memorie
return pointer_la_noua_zona_de_memorie;
}

void operator delete(void *p, [size_t dim])


{
… // elibereaza zona pointata de p
}
84
 La aplicarea acestor operatori nu se indică nicio valoare
pentru parametrul formal dim, compilatorul va calcula
automat dimensiunea obiectului pentru care se rezervă
zona de memorie, dimensiune ce se va atribui parametrului
dim
 Apelul se va face prin:

new Nume_clasa ;
sau:
new Nume_clasa(par1, par2, …, parn) ;

85
 Operatorii new şi delete au însă o particularitate:
 au o supraîncărcare globală (standard sau predefinită)
oferită de limbaj şi pentru tipurile abstracte
 pot avea o supraîncărcare locală unei clase

 Simultan poate fi folosită şi supraîncărcarea globală, printr-


o construcţie de forma :
:: new Nume_clasa ;

86
 Supraîncărcarea globală permite alocarea tablourilor de
obiecte, pe când cea locală nu
 În cazul în care se doreşte alocarea/dealocarea specială
de memorie pentru tablouri, se folosesc următoarele
sintaxe:
void *operator new[ ](size_t dim)
{
… // operatii de alocare de memorie
return pointer_la_noua_zona_de_memorie;
}

void operator delete[ ](void *p)


{
… // elibereaza zona de memorie pointata de p
87
}
Exemplul 1:
class Pozitie{
int x;
int y;
public:
Pozitie(){}
Pozitie(int oriz, int vert){
x = oriz;
y = vert;
}
void arata(){
printf("\nPozitia in plan are coordonatele: %d, %d", x, y);
}
void *operator new(size_t marime);
void operator delete(void *p);
};
void *Pozitie :: operator new(size_t marime){
printf("\n Supraincarc local operatorul new.");
return malloc(marime);
88
}
void Pozitie :: operator delete(void *p){
printf("\n Supraincarc local operatorul delete.");
free(p);
}
int main(void){
Pozitie *p1, *p2;
p1 = new Pozitie(100, 200);
if(!p1){
printf("\n Nu am putut aloca memorie pt. p1 ");
exit(0);
}
p2 = new Pozitie(10, 20);
if(!p2){
printf("\n Nu am putut aloca memorie pt. p2 ");
exit(1);
}
p1->arata();
p2->arata();
delete(p1);
89
delete(p2);
}
EXEMPLUL 2: NEW AND DELETE
#include <iostream>
#include <cstdlib>
using namespace std;

class Point {
double x, y;
public:
Point() { x = y = 0.0; }
Point(double x, double y) {
this->x = x;
this->y = y;
}
int getX() { return x; }
int getY() { return y; }
void setX(int x) { this->x = x; }
void setY(int y) { this->y = y; }
void * operator new(size_t size);
void operator delete(void * ptr);
void * operator new[](size_t size);
void operator delete[](void * ptr); 90
};
void* Point::operator new(size_t size){
void * ptr= malloc(size);
cout << “Utilizare operator new supraincarcat."<<endl;
return ptr;
}
void Point::operator delete(void * ptr){
cout << “Eliberare memorie cu operatorul delete supraincarcat" << endl;
free(ptr);
}

void * Point::operator new[](size_t size){


cout << “Utilizare operator new[] supraincarcat" << endl;
void *ptr = malloc(size);
return ptr;
}

void Point::operator delete[](void * ptr){


cout << “Eliberare memorie cu operator delete[] supraincarcat" << endl;
free(ptr);
}
91
int main(){
Point * p1; // pointer la clasa Point
if (p1 = new Point[5]) {
cout << “Obiecte Point:" << endl;
for (int i = 0; i < 5; i++)
cout << p1[i].getX() << " " << p1[i].getY() << endl;
}
else{
cout << "Eroare alocare memorie! " << endl; return 1;
}
delete[] p1;
cout << "-----------------------------------"<<endl;
Point *p2 = new Point(5, 7);
if (p2)
cout << p2->getX() << " " << p2->getY() << endl;
else
cout << "Eroare creare obiect";
if (p2)
delete p2;

92
// utilizarea functiei operator globale operator new[]() pentru tipul float

cout << "-----------------------------------"<<endl;

float *p3 = new float[10]; // apel functie apel ::operator new

delete[] p3; // ::operator delete


cin.get();
return 0;
} // main

93
 Intrebări
- Ce operatori nu pot fi supraincarcati in C++?
- Cum pot fi supraincarcati operatorii in C++?
- Cum se face supraincarcarea cu metode membre?
- Cum se face supraincarcarea cu functii friend?
- Ce operatori pot fi supraincarcati doar cu metode membre?
- Metoda de supraincarcare a operatorului = se mosteneste intr-
un proces de derivare a claselor?
- Cand este necesara supraincarcarea operatorului =?
- Cum se face supraincarcarea operatorului =?
- Cum se realizeaza supraincarcarea operatorilor [] si ()?
- Cum se realizeaza supraincarcarea operatorilor new si delete?
Variantele supraincarcate pot fi folosite pentru alocarea
dinamica a tablourilor?
- Cum se face supraincarcarea operatorilor ++ si – prefixati si
94
postfixati?
PROGRAMAREA CALCULATOARELOR-
ALGORITMI
MODUL 2
CURS 8
CUPRINS
1. Moştenirea
• Noţiuni introductive
• Moştenirea simplă
• Moştenirea multiplă
• Constructori şi destructori pentru clasa derivată
2. Clase virtuale
3. Metode virtuale
4. Clase abstracte
5. Destructori virtuali

2
1. MOŞTENIREA
Noţiuni introductive

 În practică, majoritatea claselor nu sunt în întregime


unice, ci sunt variaţii ale unor clase existente

 Prin moştenire, orice Clasă derivată dintr-o Clasă de


bază “moşteneşte” toate atributele permise ale acesteia
din urmă.
Avantaj: este evitată în acest mod rescrierea inutilă a
unor secvenţe de cod deja create

 Clasa derivată conţine membrii clasei de bază la care


se adaugă membrii proprii 3
Moștenirea – unul dintre cele 4 principii de bază ale
POO.

Principiile de bază ale POO:


➢ Încapsulare

➢ Abstractizare

➢ Polimorfism

➢ Moștenire

4
CLASE ȘI MOȘTENIREA
Relații între clase
 Intre clase putem avea următoarele relații:
 A) asociere
 B) dependență
 C) agregare
 D) moștenire

A) Asociere – relație între două sau mai multe clase care modelează
o interdependență între obiectele instanțiate din aceste clase;
 Se folosesc: numele asocierii, săgeți de navigare, roluri, indicatori
de multiplicitate într-o reprezentare UML;
Implementarea e realizată prin: pointeri la obiecte sau tablouri de
pointeri la obiecte, sau prin asocieri abstracte în clase distincte cu
atribute și comportament propriu, printr-o implementare sub forma
unui dicționar, care e o asociere multiplicativă de tipul n ... m
5
implementată cu tablouri de hashing.
• Asocierea este o relație între 2 clase bazată pe referință.
Clasa A va conține o referință la clasa B. Asocierea poate fi
reprezentată printr-o linie între aceste clase, săgeata
indicând direcția de navigare. Dacă săgeţile sunt prezente la
ambele capete, asocierea permite navigarea în ambele
sensuri.

class Asset { ... };


class Player {
Asset asset;
public Player(Asset purchasedAsset) { ... }
//Set the asset via Constructor or a setter
}; 6
B) Dependența
 Este deseori confundată cu asocierea. Dependenţa apare
când o referinţă la o clasă este primită ca parametru de o
anumită metodă. Dependenţa este reprezentată prin săgeată
punctată, pornind de la clasa dependentă spre cea de care e
dependentă

class Operations { public void Roll() { ... }


};
class Player{
public void TakeTurn(Operations op)
{ op.Roll(); ... }
};

7
C) AGREGAREA
 Caracterizată prin proprietatea a avea (to have). E o relație între
două sau mai multe clase astfel încât un obiect al unei clase
agregate devine parte constituentă a unei clase care a generat
agregarea.
 Putem avea o agregare simplă sau una compusă
 Agregarea simplă este la fel ca asocierea şi vazută deseori ca
o relaţie redundantă. O percepţie comună este că agegarea
simplă reprezintă relaţii de tipul one-to-many / many-to-many /
part-whole, care pot fi reprezentate şi prin asocieri (de aici si
redundanţa). In UML nu există o reprezentare diferită pentru ea,
unii developeri utilizează un romb gol pentru agregarea simplă.

8
class Asset { ... };
class Player {
List assets;
public void AddAsset(Asset newlyPurchasedAsset) {
assets.Add(newlyPurchasedAssest); ... }
...
};
9
AGREGAREA COMPUSĂ
 În acest caz într-o clasă se instantiază un obiect dintr-
o altă clasa. Când o clasă B este instanţiată într-o
clasă A, clasa A controlează crearea şi durata
existenţei instanţelor clasei B. Când instanţa clasei A
este distrusă, la fel va fi şi instanţa clasei B.
 O astfel de relaţie se reprezintă printr-o linie ce uneşte
cele 2 clase pe care este figurat un romb plin spre
clasa care deţine responsabilitatea creării obiectelor.
 O astfel de relaţie nu este implementată prin clase
imbricate. Clasa care este instanţiată în altă clasă
poate fi utilizată şi de alte părţi ale aplicaţiei.

10
public class Piece { ... };
public class Player
{
Piece piece = new Piece(); /*Player owns the
responsibility of creating the Piece*/
...
};

11
 D) Moștenirea - caracterizată prin proprietatea a fi (to
be). E folosită pentru a modela similaritățile și diferențele
dintre clase. Expresia, e un fel de ( is_a_kind_of ) e
folosită la nivelul clasei și e un (is_a) la nivelul
obiectelor. Doar având moștenire considerăm că avem
POO.
 Avem mai multe tipuri de moștenire, nu toate disponibile
în orice limbaj OO.

12
TIPURI DE MOȘTENIRE
 -prin specializare, subclasa este o varietate specializată
a clasei de bază
-prin specificare, clasa de bază defineste doar
comportamentul (interfeţe sau clase abstracte) care este
implementat în subclase
-prin construcţie, subclasa foloseste comportamentul
clasei de bază nefiind un subtip al ei (Stiva construită cu
Vector)
-prin extindere, subclasa adaugă noi functionalităţi clasei
de bază, dar nu va modifica nici un element moştenit
-prin limitare, subclasa limitează comportamentul clasei
de bază, fiind utilizată destul de rar
-prin combinare, când de fapt avem moştenire multiplă,
bazată pe mai multe clase de bază. 13
 Clasa CPolygon conţine membri ce sunt comuni
celorlalte două clase
 Clasele CRectangle şi CTriangle vor conţine și membri
ce corespund unor caracteristici specifice
14
 Clasa de bază nu este afectată de crearea unei clase
derivate din ea şi nu trebuie recompilată

 O clasă derivată nu poate modifica definiţia clasei de bază

 Dintr-o clasă de bază pot fi derivate mai multe clase şi


fiecare clasă derivată poate fi folosită ca bază pentru alte
clase derivate:
 se pot obţine astfel ierarhii de clase

 O clasă poate moşteni proprietăţile mai multor clase de


bază (moştenire multiplă)
 Clasa de bază se mai numeşte şi superclasă, iar clasa
derivată se mai numeşte şi subclasă
15
TIPURI DE MOȘTENIRE C++

16
Moştenirea simplă

 Declararea unei clase derivate se face astfel :

class Clasa_derivata : [specificator_de_acces] Clasa_baza


{
// corp clasa derivata
};

 Specificatorul de acces permite controlul asupra membrilor


clasei de bază
 Dacă nu este prezent se consideră că acesta este implicit
de tip private
17
 Ce moşteneşte o clasă derivată:
 toate datele membre ale clasei de bază (cele private nu
vor fi accesibile din clasa derivată)
 toate metodele (funcţiile) membre ordinare din clasa de
bază
 aceeaşi organizare iniţială a datelor ca în clasa de bază

 Ce nu moşteneşte o clasă derivată:


 constructorii clasei de bază
 destructorul clasei de bază
 metodele membre ce supraîncarcă operatorul de
atribuire în clasa de bază
 funcţiile prietene ale clasei de bază

18
 Ce poate adăuga o clasă derivată:
 date membre noi
 metode membre noi
 constructori
 destructor
 funcţii prietene
 Specificatorul de acces dă măsura în care clasa derivată
poate accesa membrii moşteniţi din clasa de bază
 Tipuri de moştenire

Moştenire
Clasa de bază
public protected private

Membru public public protected private

Membru protected protected protected private


19
Membru private inaccesibil inaccesibil inaccesibil
 class Derivata : public Baza { ..... }
 membrii privaţi din Baza sunt inaccesibili în Derivata
 membrii protejaţi din Baza devin membri protejaţi în
Derivata
 membrii publici din Baza devin membri publici în
Derivata

 class Derivata : protected Baza { ..... }


 membrii privaţi din Baza sunt inaccesibili în Derivata
 membrii protejaţi şi cei publici din Baza devin membri
protejaţi în Derivata

 class Derivata : private Baza { ..... }


 membrii privaţi din Baza sunt inaccesibili în Derivata
 membrii protejaţi şi cei publici din Baza devin membri 20
privaţi în Derivata
 Moştenirea public

class Baza
{
protected:
int i, j;
public:
void setI(int a) { I = a; }
void setJ(int b) { j = b; }

void afiseaza( ) {
cout << i << ", " << j << " \n ";
}
};//Baza

21
class Derivata : public Baza {

public:
int inmulteste( ) {
return i * j; // corect, i si j raman protected
}
};//Derivata

int main(void)
{
Derivata obiect_derivat;
//obiect_derivat.i = 5; // gresit, i ramane protected
obiect_derivat.setI(12); //din Baza
obiect_derivat.setJ(17); // din Baza
obiect_derivat.afiseaza( ); // din Baza
cout<< "\n " este: " <<obiect_derivat.inmulteste( );

22
}
 Moştenirea protected
class Baza
{
int x;
protected:
int y;
public:
int z;
Baza(int x=1, int y=1 ) {
this->x=x;
this->y=y;
}
void arata( ) {
cout << "\n --------Clasa de baza------";
cout << "\n Valoarea variabilei private x: "<<x;
cout << "\n Valoarea variabilei publice y: "<<y;
} 23
};//Baza
class Derivata: protected Baza
{
public:
void do_this(void) {
//x = 5; // gresit, x este private in Baza
y = 7; // corect, y este protected in Baza, ramane protected
z = 15; // corect, z este public in Baza, devine protected
}
};

int main(void){
Baza ob1;
Derivata ob2;
ob1.arata( ); // public in Baza cout<<ob1.z;
ob2.do_this( );
//ob2.arata( ); // gresit, arata( ) devine protected in Derivata
24

}
 Moştenirea private

class Baza
{
protected: int a, b;
public:
int setA(int a){ this->a=a;}
int setB(int b){this->b=b;}
int aduna() {
return a+b;
}
int scade() {
return a-b;
}
void afis_baza( ) {
cout << a << " " << b << "\n";
} 25
};
class Derivata : private Baza
{
public:
int inmulteste() {
return a*b;
}
};

int main(void){
Baza obiect_baza;
obiect_baza.setA(1);
obiect_baza.setB(2);
Derivata obiect_derivat;
cout<< " \nProdusul este= " <<obiect_derivat.inmulteste();
//obiect_derivat.aduna(); // eroare, devine private

} 26
 Este posibil ca membrii clasei de bază să fie exceptaţi
individual de la modul de acces stabilit prin declaraţia
clasei derivate, astfel încât să-şi păstreze atributele din
clasa de bază:

class A { class B : private A


private : {
int x; …
void fx(void); public :
public : A::fy();
int y; protected :
void fy(void); A::z;
protected : };
int z;
void fz(void); 27
};
Moştenirea multiplă

class Clasa_derivata : lista_Clase_baza


{
// corp clasa
};

lista_Clase_baza: [specificator_acces] Clasa_bazai, …

 Fiecare clasă de bază poate fi prefixată la moștenire de


specificatorul private, public sau protected
 Dacă specificatorul de acces lipseşte se consideră implicit
că este unul private

28
Exemplul 1:
class Baza1 { class Baza2 {
protected: protected:
int x; int y;
public: public:
void afiseaza_x( ) { void afiseaza_y( ) {
cout << " valoarea lui y
cout << " valoarea lui x este:
este: " << y <<" \n ";
“ << x <<”\n ";
}
}
void afis( ) {
void afis( ) { cout << " y = " << y << " \n
cout << " x = “ << x << " \n "; ";
} }
};//Baza1 };//Baza2

29
class Derivata: public Baza1, public Baza2 {
public:
void initializeaza(int i, int j) {
x = i;
y = j;
}
void afis( ) {
Baza1::afis( );
Baza2::afis( );
}
};
int main(void){
Derivata obiect_derivat;
obiect_derivat.initializeaza(100, 200);
obiect_derivat.afiseaza_x( ); // metoda mostenita din Baza1
obiect_derivat.afiseaza_y( ); // metoda mostenita din Baza2
obiect_derivat.afis(); // metoda din clasa Derivata
30
}
Observații:

 Clasa derivată poate conţine redefiniri (override) ale


unor metode din clasa de bază :
 în clasa derivată pot exista metode cu acelaşi nume
ca și în clasa de bază, având sau nu aceeaşi
semnătură. Spunem că avem redefinire doar dacă
au exact același prototip.
 variantele din clasele de bază pot fi accesate din
clasa derivată utilizând calificarea completă:
Nume_clasă::nume_membru

31
 În cazul moştenirii multiple, dacă clasele de bază conţin
membri cu acelaşi nume, referirea acestora printr-un obiect
din clasa derivată poate conduce la ambiguităţi:
 pentru evitarea acestora se utilizează calificarea
completă
obiect_clasa_derivata.Nume_clasa_baza::nume_membru
 Exemplu : dacă în exemplul anterior în clasa derivată nu
avem funcția afis(), printr-un obiect din clasa derivată
putem apela una din metodele afis() din clasele de bază.

obiect_derivat.afis( ); // ambiguitate

obiect_derivat.Baza1::afis( ); //corect
32
Exemplul 2:

class CPolygon
{
protected:
int width, height;
public:
void set_values (int a, int b) {
width=a; height=b;
} // scop didactic, se recomanda setteri individuali
};

class COutput
{
public:
void output (float i) {
cout << i << endl;
33
}
};
class CRectangle: public CPolygon, public COutput
{
public: int area ( ) { return (width * height); }
};

class CTriangle: public CPolygon, public COutput


{
public: float area ( ){ return (width * height / 2.); }
};

int main ( ) {
CRectangle re;
CTriangle trgl;
re.set_values (4,5); // metoda mostenita din CPolygon
trgl.set_values (4,5);
re.output (re.area( )); // metoda mostenita din COutput
trgl.output (trgl.area( ));
34
}
Constructori şi destructori pentru clasa derivată

 Regulile de funcţionare ale constructorilor şi destructorilor


rămân valabile pentru clasele derivate, cu unele aspecte
particulare

 Ce se întâmplă la crearea unui obiect din clasa derivată:


 se alocă spaţiu pentru întregul obiect (pentru datele
membre din clasa de bază și datele membre din clasa
derivată)
 se apelează constructorul clasei de bază pentru a
iniţializa datele membre moştenite din clasa de bază
 se apelează constructorul din clasa derivată pentru a
iniţializa datele membre adăugate în clasa derivată
35
 Ce se întâmplă la distrugerea unui obiect din clasa
derivată:
 se apelează destructorul clasei derivate
 se apelează destructorul clasei de bază
 este eliberat spaţiul alocat pentru întregul obiect

36
class A {
// corp clasa
};
class B : public A {
// corp clasa
};
class C : public B {
// corp clasa
};

 În cazul instanţierii unui obiect din clasa C, ordinea


creării obiectelor este:
A::A( ) -> B::B( ) -> C::C( );

 Ordinea distrugerii obiectelor este:


37
C::~C( ) -> B::~B( ) -> A::~A( );
 !!! În cazul în care în clasa de bază există doar
constructor cu parametri, atunci trebuie să avem
constructor explicit și în clasa derivată, care trebuie să
specifice apelul constructorului clasei de bază în lista de
iniţializare
 De regulă, constructorul clasei derivate conţine în lista de
parametri şi acei parametri care vor fi transmişi
constructorului clasei de bază:
Clasa_derivata ::Clasa_derivata(lista_parametri) :
Clasa_baza(parametri);
 Exemplu:
B::B(int x, int y) : A(y) {
// corp constructor
38
}
Exemplu
// clasa de baza
class Pozitie {
protected :
int x, y;
public :
Pozitie (int=0, int=0);
void afisare( );
void deplasare(int, int);
};
Pozitie::Pozitie(int abs, int ord) {
x = abs; y=ord;
cout << "Constructor \"Pozitie\", ";
afisare( );
}
void Pozitie::afisare( ) {
cout << "coordonate: x = " << x << ", y = " << y << "\n";
}
void Pozitie::deplasare(int dx, int dy) { 39
x += dx; y+=dy;
}
// clasa derivata
class Punct: public Pozitie {
int vizibil;
char culoare;
public:
Punct(int=0, int=0, char='A');
void arata( ) {
vizibil = 1;
}
void ascunde( ) {
vizibil = 0;
}
void coloreaza(char c) {
culoare = c;
}
void deplasare(int, int);
40
void afisare( );
};
// constructor
Punct::Punct(int abs, int ord, char c):Pozitie(abs, ord)
{
vizibil = 0;
culoare = c;
cout << "Constructor \"Punct\", ";
afisare( );
}

// redefinire functie de afisare in clasa derivata


void Punct::afisare( ) {
cout << "Pozitie: x = " << x << ", y = " << y;
cout << ", culoare: " << culoare;
if(vizibil)
cout << ", vizibil \n";
else
41
cout << ", invizibil \n";
}
// redefinire functie de deplasare in clasa derivata
void Punct::deplasare(int dx, int dy)
{
if(vizibil) {
cout << "Deplasare: ";
afisare( );
}
x += dx;
y += dy;
if(vizibil) {
cout << "la coordonatele: ";
Pozitie::afisare( ); //metoda din clasa Pozitie
}
} 42
// program ce utilizeaza clasa derivata
int main(void){
Punct p0(1, 1, 'V');
Punct p1(p0); //copy-constructor
p1.arata();
p1.deplasare(10,10);
}

43
Copy-constructorul în clasa derivată

 Constructorul de copiere al clasei derivate este


răspunzător de iniţializarea corectă a datelor moştenite
de la clasa de bază
 De aceea se recomandă utilizarea unui astfel de
constructor de copiere:

Clasa_deriv ::Clasa_deriv(const Clasa_deriv &nume_par) :


Clasa_baza(parametri);

44
În cazul moştenirii multiple:
 ordinea în care sunt apelaţi constructorii claselor de
bază este aceeaşi cu ordinea specificării claselor de
bază în declaraţia clasei derivate
 această ordine poate diferi de ordinea specificată în lista de
iniţializare din constructorul clasei derivate
 În general, ordinea operaţiilor în cazul creării obiectelor
derivate este următoarea:
 se apelează constructorii claselor de bază în ordinea
precizată în declaraţia clasei derivate
 se apelează constructorii altor obiecte membre din
clasa derivată, în ordinea declarării lor în clasa
derivată
 se apelează constructorii clasei derivate
 La distrugerea obiectelor derivate, operaţiile se
desfăşoară în ordine inversă 45
2. CLASE VIRTUALE
 Considerăm următoarea situaţie:
class BB {
protected : int x;
BB

};
class B1 : public BB {
// corp clasa B1
}; B2
class B2 : public BB {
// corp clasa
};
class D : public B1, public B2 { D
// corp clasa
};

 Un obiect al ultimei clase derivate D conţine duplicate ale


datelor membre ale primei clase de bază BB 46
 Pentru a distinge între datele membre se poate folosi
operatorul de rezoluţie (::) şi calificarea completă astfel:
BB::B1::x
BB::B2::x
 Dacă o instanţă a clasei D ar vrea să acceseze
variabila x printr-o sintaxă de genul:
D obiect;
obiect.x = valoare;
Apare o ambiguitate:
 compilatorul nu poate decide prin care din clasele B1
sau B2 să acceseze variabila din clasa de bază
 Putem folosi sintaxa object.B1 :: x = 1; dacă
accesăm x prin B1, dar sintaxa e considerată
47
complicată
 Există posibilitatea includerii unui singur exemplar al clasei
de bază primare prin declararea acesteia ca fiind clasă
virtuală în declaraţiile claselor B1 si B2:
class BB {
protected : int x;

};

class B1 : virtual public BB {


// corp clasa
};

class B2 : virtual public BB {


// corp clasa
};

class D : public B1, public B2 {


48
// corp clasa
};
 În acest caz, un obiect al clasei D va conţine numai un
obiect al clasei de bază comune BB, iar clasele B1 şi B2
vor partaja acest obiect comun → moștenire hibridă

 Un obiect al unei clase derivate dintr-o clasă de bază


virtuală nu conţine direct obiectul clasei de bază comune,
ci conţine de fapt un pointer către acesta

49
 Dacă într-o ierarhie de clase, unele instanţe ale clasei de
bază sunt declarate virtuale, iar altele sunt declarate non-
virtuale, atunci obiectele claselor derivate vor conţine câte
un obiect al clasei de bază pentru fiecare instanţă non-
virtuală şi un singur obiect al clasei de bază pentru toate
instanţele virtuale

 Specificatorul virtual deşi apare în declaraţiile claselor B1


şi B2 are efect numai asupra claselor derivate din acestea

50
 Constructorul clasei derivate D trebuie să precizeze
transferul de date către constructorul clasei de bază BB
pentru crearea unei copii unice a obiectului BB:
D::D(lista_param) : B1(lista_param), B2(lista_param),
BB(lista_param) {…}

 Indiferent de poziţia într-o ierarhie de clase, un obiect al


unei clase de bază virtuale este întotdeauna construit
înaintea obiectelor non-virtuale din aceeaşi ierarhie

 Dacă într-o ierarhie de clase, o clasă de bază este


declarată virtuală folosind specificatori de acces diferiţi,
atunci se va propaga specificatorul cel mai permisiv (de
exemplu dintre specificatorii private şi public se va
propaga specificatorul public)
51
EXEMPLU CU CONTAINER STRING :
#include <iostream>
#include <string>
using namespace std;

class Person {
private:
string ptype;
string name;
public:
Person( const string & t, const string & n ) : ptype( t ), name( n ){ }
virtual ~Person( ) { }
const string& getName( ) {
return name;}
const string getPtype( ) {
return ptype; }
};//Person class
52
class Student : virtual public Person {
private:
int hours;
public:
Student( const string& n, int h ) : Person( "Student", n ), hours(h) { }
int getCreditHours( ) {
return hours;
}
}; //Student class

class Employee : virtual public Person {


private:
int hours;
public:
Employee( const string& n, int h ) : Person( "Employee", n), hours(h) { }
int getVacationHours( ) {
return hours;
}
}; //Employee class 53
class StudentEmployee : public Student, public Employee
{
public:
StudentEmployee( const string& n, int ch, int vh ) :
Employee( "ignored", vh ),
Student( "ignored", ch ),
Person( "StudentEmployee", n ) { }
};//StudentEmployee class
int main( ) {
StudentEmployee se("Popescu", 120, 15 );
cout << "Name: " << se.getName( ) << endl;
cout << "Type: " << se.getPtype( ) << endl;
cout << "Credits: " << se.getCreditHours( ) << endl;
cout << "Vacation: " << se.getVacationHours( ) << endl;

}//main

Obs: constructorii se apeleaza in ordinea Person, Student, Employee 54


//Exemplu cu tablouri de caractere:
class Person {
private:
char ptype[20];
char name[20];
public:
Person( const char t[], const char n[] )
{
strcpy(ptype, t);
strcpy(name, n);
}
~Person( )
{}
const char* getName( ) {
return name;
}
const char* getPtype( ) {
return ptype;
53
}
};
class Student : virtual public Person
{
private:
int hours;
public:
Student( const char n[], int h ) : Person( "Student", n ), hours(h)
{}

int getCreditHours( ) {
return hours;
}
};

54
class Employee : virtual public Person
{
private:
int hours;
public:
Employee( const char n[], int h ) : Person( "Employee", n), hours(h)
{}

int getVacationHours( ) {
return hours;
}
};

55
class StudentEmployee : public Student, public Employee
{
public:
StudentEmployee( const char n[], int ch, int vh ) :
Person( "StudentEmployee", n ),
Student( "ignored", ch ),
Employee( "ignored", vh )
{}
};

int main( ){
StudentEmployee se( “Popescu", 120, 15 );

cout << "Name: " << se.getName( ) << endl;


cout << "Type: " << se.getPtype( ) << endl;
cout << "Credits: " << se.getCreditHours( ) << endl;
cout << "Vacation: " << se.getVacationHours( ) << endl;
cin.get()ș 56
}
3. METODE VIRTUALE
 O metodă virtuală este acea metodă care este definită cu
specificatorul virtual în clasa de bază şi apoi poate fi
redefinită în clasele derivate:
 au implementări diferite în clasele derivate, dar aceeași
semnătură

 Redefinirea din clasele derivate are prioritate faţă de


definirea iniţială din clasa de bază, în cazul apelării
acesteia prin intermediul unui obiect instanţiat din clasa
derivată
 De obicei, o funcţie virtuală defineşte o clasă generală de
acţiuni, iar varianta redefinită introduce o metodă specifică
59
Observații:
- În cazul utilizării metodelor virtuale, prototipul
funcţiilor din clasele derivate trebuie să fie exact
acelaşi ca cel al funcţiei originale (semnătura)
 dacă lista de parametri diferă şi metoda originală
este declarată ca fiind virtuală, mecanismul de
funcţie virtuală este ignorat

 Când sunt utilizate obişnuit (apel prin nume obiect),


metodele virtuale se comportă la fel ca alte metode
membre ale clasei
 Comportamentul specific apare atunci când sunt
apelate printr-un pointer spre clasa de bază, care poate
referi și obiecte din clasa derivată.
 Un pointer al clasei de bază poate fi folosit pentru a
indica spre orice obiect dintr-o clasă derivată din
aceasta (upcasting)
60
UPCASTING/DOWNCASTING
 Un pointer către o clasă de bază poate fi utilizat pentru a
indica spre un obiect dintr-o clasă derivată din acea clasă de
bază, fără a specifica ceva suplimentar – proces numit
upcasting. Tipul obiectului decide care metoda va fi apelată.
 Un obiect dintr-o clasă derivată poate fi asignat unui obiect
din clasa de bază, fără a specifica nimic în plus – upcasting
 Un pointer la o clasă derivată poate fi asociat unui obiect din
clasa de bază, folosind un cast explicit spre pointer de tipul
clasei derivate – proces numit downcasting (in anumite
situatii !!!)
cout<<“\n Downcasting1: \n;
Punct *pdown; // pointer spre clasa derivata
61
pdown=(Punct*)&pp0; // obiect din clasa de baza
pdown-> afisare(); // metoda din clasa de baza
class Baza {
public:
virtual void functie( ) {
printf("\n Functia din clasa de baza\n");
}
};

class Derivat1: public Baza {


public:
void functie( ) {
printf("\n Functia din prima clasa derivata\n");
}
};

class Derivat2: public Derivat1 {


public:
void functie( ) {
printf("\n Functia din a doua clasa derivata\n");
} 62
};
int main(void) {
Baza *p, ob_baza;
Derivat1 ob_derivat1;
Derivat2 ob_derivat2;
p = &ob_baza;
p->functie( );
p = &ob_derivat1; // upcasting
p->functie( );
p = &ob_derivat2;
p->functie( );
}

63
EXEMPLU:
#include <iostream>
using namespace std;

class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b) {
width=a; height=b; }
int getWidth() { return width;}
int getHeight() { return height; }
virtual float area(void) {return 0; }
};//CPolygon class

class CRectangle: public CPolygon {


public:
float area (void) {
return (width * height); } 64
};//CRectangle
class CTriangle: public CPolygon {
public:
float area (void) { return (width * height / 2.); }
};//CTriangle
int main (void) {
CRectangle rect; CTriangle trgl; CPolygon poly;
//upcasting
CPolygon* pPoly;
pPoly = &rect;
pPoly->set_values(1, 2);
cout << "Arie dreptunghi: " << pPoly->area() << endl; // CRectangle
pPoly = &trgl;
pPoly->set_values(3, 4);
cout << "Arie triunghi: " << pPoly->area() << endl; // CTriangle
pPoly = &poly;
65
pPoly->set_values(5, 6); // met. din CPolygon
cout << "Arie poligon (init): " << pPoly->area() << endl; // CPolygon
//upcasting
poly=trgl; // 3,4
cout<< "Valori obiect poligon dupa upcasting:
width="<<poly.getWidth()<<" height="<<poly.getHeight()<<endl;
cout << "Arie poligon (trgl): " << poly.area( ) << endl;

// poly preia valorile atributelor din trgl, dar se apeleaza


// metoda area() din clasa de baza CPolygon

66
 Când un astfel de pointer indică spre un obiect derivat ce
conţine o metodă virtuală, compilatorul C++ determină
care versiune a metodei va fi apelată, în funcţie de tipul
obiectului spre care indică acel pointer

 Varianta cu care se va lucra se stabileşte la execuție,


motiv pentru care se foloseşte denumirea de “legătură
dinamică” (dynamic binding), spre deosebire de “legătura
statică” în care varianta cu care se lucrează este stabilită
în etapa de compilare

 Astfel se pot executa versiuni diferite ale funcţiei virtuale


în funcţie de tipul obiectului (polimorfism dinamic)
67
 Alte caracteristici:
 o metodă rămâne virtuală indiferent de câte ori este
moştenită
 dacă o clasă derivată nu redefineşte o metodă
virtuală, atunci un obiect dintr-o clasă derivată va
folosi metoda din clasa de bază
 metodele statice nu pot fi funcţii virtuale
 constructorii nu pot fi funcţii virtuale
 destructorii pot fi funcţii virtuale

68
4. CLASE ABSTRACTE
 Sunt clase care nu sunt utilizate direct, ci furnizează un
schelet pentru alte clase ce vor fi derivate din acestea
 De obicei, toate metodele membre ale unei clase abstracte
sunt virtuale şi au implementări vide urmând să fie redefinite
în clasele derivate

 O metodă virtuală ce nu are implementare se numeşte


metoda pur virtuală (funcţie virtuală pură):
 doar defineşte o interfaţă pentru obiectele claselor derivate
 O metoda pur virtuală face clasa în care apare să fie o
clasă abstractă
 Metoda pur virtuală trebuie definită în clasele derivate sau
redeclarată ca metoda pur virtuală în clasele derivate
69
 Declararea unei metode pur virtuale:
virtual tipReturnat numeMetoda( ) = 0;

Observație: O clasă abstractă nu poate fi instanţiată (nu se


pot declara obiecte având acest tip), în schimb se pot
declara pointeri la o clasă abstractă

 Metodele pur virtuale sunt destinate moştenirii:


 definesc o interfaţă pentru o anumită caracteristică
ce urmează a fi implementată într-o clasă derivată

70
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b) {
width=a; height=b;
}

virtual float area (void) =0;

void print_area (void) {


cout << this->area( ) << endl;
}
}; 71
class CRectangle: public CPolygon {
public:
float area (void) {
return (width * height);
}
};

class CTriangle: public CPolygon {


public:
float area (void) {
return (width * height / 2.);
}
};
72
int main ( ) {
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = &rect;
CPolygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << "Apel direct pentru metoda pur virtuala\n";
cout << "\tArie dreptunghi: " << ppoly1->area( ) << endl;
cout << "\tArie triunghi: " << ppoly2->area( ) << endl;
cout << "Apel indirect pentru metoda pur virtuala\n";
cout << "\tArie dreptunghi: " ;
ppoly1->print_area( );
cout << "\tArie triunghi: " ;
ppoly2->print_area( );
73
}
5. Destructori virtuali

 În cazul utilizării pointerilor către clasa de bază care


referă obiecte din clasa derivată, pot să apară probleme
la eliminarea unor obiecte (cele derivate)
 Scopul declarării destructorilor virtuali este acela de a
se asigura apelul tuturor destructorilor implicaţi într-o
ierarhie, în ordinea corectă:
 destructorii claselor derivate vor deveni la rândul lor
virtuali

 Destructorii virtuali vin în contradicţie cu:


 scopul metodelor virtuale (de a fi moştenite şi
redefinite în clasele derivate)
 caracteristicile destructorilor (nu pot fi moşteniţi, ca
urmare nu pot fi ascunşi sau redefiniţi în clase 74
derivate)
Exemplul 1:
class Base {
// membri comuni
public:
Base( ) { cout<<"Constructor: Base"<<endl;}
~Base( ) { cout<<"Destructor : Base"<<endl;}
// virtual ~Base( ) { cout<<"Destructor : Base"<<endl;}
};

class Derived: public Base {


// membri specifici
public:
Derived( ) { cout<<"Constructor: Derived"<<endl;}
~Derived( ) { cout<<"Destructor : Derived"<<endl;}
};

int main( ){
Base *Var = new Derived( );
// alte prelucrari
delete Var; 75

}
76
Exemplul 2:
enum Color {Co_red, Co_green, Co_blue};

// clasa de baza abstracta


class Shape {
protected:
int xorig;
int yorig;
Color co;
public:
Shape(int x, int y, Color c) : xorig(x), yorig(y), co(c) { }
virtual ~Shape( ) { cout << "~Shape \n"; } // destructor virtual
virtual void draw( ) = 0; // functie virtuala pura
};
77
// clasa linie (intre origine si un punct destinatie)
class Line : public Shape {
int xdest;
int ydest;
public:
Line(int x, int y, Color c, int xd, int yd) :
xdest(xd), ydest(yd), Shape(x, y, c) { }
~Line( ) {
cout << "~Linie\n";
} // destructor virtual
void draw( ) {
cout << "Linie" << "(";
cout << xorig << ", " << yorig << ", " << int(co);
cout << ", " << xdest << ", " << ydest;
cout << ")\n";
} 78
};
// Clasa cerc : cerc cu centru si raza
class Circle : public Shape {
int raza;
public:
Circle(int x, int y, Color c, int r) : raza(r), Shape(x, y, c) { }
~Circle( ) {
cout << "~Cerc\n";
} // destructor virtual
void draw( ) {
cout << "Cerc" << "(";
cout << xorig << ", " << yorig << ", " << int(co);
cout << ", " << raza;
cout << ")\n";
}
};
79
int main(void)
{
const int N = 4;
int i;
Shape* sptrs[N];
sptrs[0] = new Line(1, 1, Co_blue, 4, 5);
sptrs[1] = new Line(3, 2, Co_red, 9, 75);
sptrs[2] = new Circle(5, 5, Co_green, 3);
sptrs[3] = new Circle(3, 3, Co_red, 10);
for (i = 0; i < N; i++)
sptrs[i]->draw( );
for (i = 0; i < N; i++)
delete sptrs[i];
80
}
PROGRAMAREA CALCULATOARELOR –
ALGORITMI
MODUL 2
I/E
1

CURS 9
CUPRINS
1. Sistemul de intrări-ieşiri din C++
• Introducere
• Clasa ios
 Operaţii de I/E cu format
 Utilizarea funcţiilor width(), precision() şi fill()
 Funcţiile de I/E de tip “manipulator”
 Crearea propriilor funcţii de tip manipulator
 Verificarea stării sistemului de I/E
• Clasa ostream
• Clasa istream
• Funcţiile de tip inserter şi extractor
 Redefinirea operatorului de ieşire
 Redefinirea operatorului de intrare
• Operaţii de intrare-ieşire pe fişiere
• Operaţii de intrare-ieşire pe şiruri de caractere 2
1. SISTEMUL DE INTRĂRI-IEŞIRI DIN C++
Introducere

 Limbajul C++ conţine toate funcţiile din biblioteca de I/E a


limbajului C, dar ne pune la dispoziţie şi un sistem propriu
de I/E orientat pe obiecte, care ne permite să citim/scriem
obiecte de diferite tipuri

 Sistemul de I/E din C++ poate fi redefinit în cadrul


propriilor clase

3
 Sistemul de I/E din C++ operează prin stream-uri (fluxuri)
 Un stream (flux) este un dispozitiv logic, care fie produce,
fie consumă informaţie şi este cuplat la un dispozitiv fizic
prin intermediul sistemului de I/E din C++

 Un flux poate fi de intrare, de ieşire sau bidirecţional


 Citirea datelor de la un flux de intrare se numeşte
extracţie și este realizată prin operatorul de extracţie >>
sau cu o funcţie membră a clasei iostream
 Scrierea datelor la un flux de ieşire se numeşte inserţie şi
este realizată prin operatorul de inserţie << sau cu o
funcţie membră a clasei iostream

4
Flux de intrare
Dispozitiv de intrare Program
>> (extractie)

Flux de iesire
Dispozitiv de iesire Program
<< (insertie)

Flux bidirectional

Dispozitiv de i/e Program

5
 La execuţia unui program în C++ se deschid în mod
automat următoarele stream-uri predefinite (ca urmare nu
trebuie declarate) :
 cin Intrarea standard Tastatura
 cout Ieşirea standard Ecranul
 cerr Eroarea standard Ecranul
 clog Versiunea “tampon” a lui cerr Ecranul

 Unica diferenţă dintre obiectele cerr şi clog constă în


modul în care se execută golirea lor :
 în cazul lui cerr după fiecare inserare, la clog doar la
umplerea bufferului

 La terminarea execuţiei unui program în C++ aceste


fluxuri sunt închise automat
6
 C++ defineşte sistemul său de I/E în mai multe fişiere antet :
 iostream.h (iostream): declară obiecte care controlează
operații de citire/scriere pe fluxuri standard, operaţii de nivel
scăzut (fluxuri de caractere) sau de nivel înalt (obiecte
specifice, predefinite sau definite de utilizator);
• Aceste obiecte sunt instanțe ale claselor istream, ostream,
moștenite la rândul lor din clasa ios.
 fstream.h (fstream): conţine clase derivate din istream,
ostream și iostream, pentru operaţii de I/E pe fişiere;
 aici sunt definite clasele ifstream, ofstream, fstream;

 strstream.h (sstream): conţine clase derivate din istream,


ostream, iostream și streambuf, pentru operaţii de I/E pe şiruri
de caractere;
 aici sunt definite clasele istrstream, ostrstream, iostrstream;
(istringstream, ostringstream, stringstream)
 iomanip.h (iomanip): conţine definiţia unui set de manipulatori
ce operează pe fluxuri pentru a produce un anumit efect;
7
BIBLIOTECA I/E

8
 Operaţiile de I/E se bazează pe un model pe două nivele

 Nivelul superior asigură operaţii de I/E formatate pentru


obiecte specifice (predefinite sau definite de utilizator)

 Nivelul inferior se ocupă cu operaţii de I/E neformatate,


pentru fluxuri de caractere:
 acest nivel se bazează pe obiecte de tipul streambuf
(flux cu zonă tampon) şi permite ascunderea detaliilor
fizice de operare a DP
 Utilizatorul nu lucrează direct cu aceste obiecte, ci
indirect prin intermediul fluxurilor: la crearea unui obiect
stream, acestuia i se asociază automat un obiect de tipul
streambuf

9
Clasa ios

 Această clasă precizează facilităţi comune operaţiilor de


intrare şi de ieşire

- utilizează un obiect de tipul streambuf pentru transferul


datelor,
- menţine informaţii despre starea acestuia
- permite specificarea informaţiilor de formatare pentru
obiectele din clasele derivate istream şi ostream

10
 Clasa ios este clasa de bază pentru clasele istream,
ostream, fstream, stringstream

 Clasa ios moștenește clasa ios_base. În clasa ios_base


sunt definite niște tipuri de date cu semnificația de măști
de biți (BitmaskType) :
 fmtflags : pentru formatarea operaţiilor de I/E
 iostate: pentru informaţii de stare
 openmode: conţine modurile de deschidere ale
fluxurilor
 seekdir: pentru căutarea relativă în fluxuri.

Un BitmaskType definește un tip care poate fi utilizat


pentru a reprezenta un set de valori constante sau orice
combinație a acestor valori. Aceste constante au asociate
nume, care sunt accesate prin ios_base::nume_const
sau ios::nume_constanta 11
Operaţii de I/E cu format

 Sistemul de I/E din C++ ne permite să formatăm datele


într-un mod similar celui folosit de funcţia printf()
 Fiecare stream din C++ are asociat un număr de
“indicatori de format” (flaguri), care determină modul de
afişare
 Aceştia sunt codificaţi în date membre statice de tip
fmtflags din clasa ios_base în versiunile mai noi ale
limbajului C++ (versiunile anterioare foloseau un long
int)
 Când se poziţionează un flag de format este activată
caracteristica respectivă, iar când acesta este şters se
utilizează formatul implicit
12
skipws - determină citirea/ignorarea caracterele de tip
whitespace (spaţiu, tab, newline) la citire
left - determină alinierea rezultatului la stânga
right - determină alinierea rezultatului la dreapta
internal - o valoare numerică se va extinde astfel încât
să se completeze câmpul, prin inserarea de
spaţii între semn și valoarea numerică; pentru
valoare nenumerică este echivalent cu flagul
right
dec - (implicit) afişare în zecimal
oct - afişarea în octal
hex - se afişează rezultatul în hexazecimal
showbase - determină afişarea simbolului bazei de
numerație în care se face afișarea
boolalpha - citește/scrie elementele booleene ca 13
stringuri (true și false).
showpoint - determină afişarea tuturor zerourilor şi a
punctului zecimal ale unei valori în format
virgulă mobilă
showpos - determină afişarea semnului înaintea
valorilor numerice pozitive
scientific - generează afişarea numerelor în format
virgulă mobilă, folosindu-se notaţia ştiinţifică
fixed - când este poziţionat, valorile în format
virgulă mobilă sunt afişate folosind notaţia
obișnuită
unitbuf - streamul este eliberat după fiecare operație
de ieșire, ceea ce determină scrierea în
dispozitivul fizic cuplat la stream
uppercase - determină afișarea cu majusculă a
caracterelor generate de stream ( de ex. 0X 14
pentru afișarea unei valori în hexazecimal)
 Pentru a seta un flag de format, utilizăm metoda setf( ),
membră a clasei ios:
fmtflags setf(fmtflags mask); // long setf ( long mask );
fmtflags setf(fmtflags mask, fmtflags unset);
// long setf (long mask, long unset);
această metodă activează flagurile specificate de
argumentul mask, toţi ceilalţi indicatori rămân neschimbaţi
 a doua varianta are în plus un parametru care specifică
indicatorii ce vor fi șterși
 returnează starea anterioară a flagurilor
 prin aplicarea unui operator de tip OR pe bit, putem
poziţiona mai mulţi indicatori de stare

 Exemplu:
15
cout.setf( ios::showbase | ios::hex);
 Metoda unsetf( ), membră a clasei ios, şterge unul sau
mai mulţi indicatori de format:

void unsetf (fmtflags flags); // void unsetf ( long flags );

 Metoda:
 şterge indicatorii specificaţi de parametrul flags
 toţi ceilalţi indicatori rămân neschimbaţi

16
#include <iostream>
using namespace std;

int main(){
// afisarea valorilor folosind pozitionarile implicite
cout<< 123.33 << " salut! " << 100 <<'\n';
cout<< 10 << ' ' << -10 <<'\n';
cout << 100.0 <<'\n';
// schimbam formatul
cout.setf (ios::scientific| ios::showpos);
cout << 123.33 << " salut! " << -100 <<'\n';
cout.unsetf(ios::dec);
cout.setf (ios::hex);
cout <<10 << ' ' << -10 << '\n';
cout.unsetf (ios::scientific| ios::showpos);
cout.setf (ios::showpoint); 17

cout << 100.00 << '\n'; }


 Metoda flags( ), membră a clasei ios oferă informații
despre starea curentă a fiecărui indicator de format,
codificat într-o variabilă de tip fmtflags:
 Prototipuri :
fmtflags flags( ); //long flags( )
fmtflags flags(fmtflags f); //long flags( long f )

 în a doua variantă, metoda flags( ) ne permite să


poziţionăm toţi indicatorii asociaţi unui stream la
valorile specificate de argument; șterge toți indicatorii
ce nu sunt explicit specificați în argument

18
Utilizarea metodelor width( ), precision( ) şi fill( )

 Stabilesc lăţimea câmpului, precizia şi caracterul de


inserat
 Prototipurile metodei width( ) sunt:
int width( ); // returneaza valoarea curenta
int width(int w); // stabileste latimea minima
// returneaza valoarea anterioara

 Dacă o valoare utilizează un spaţiu mai mic decât cel


specificat, câmpul este completat cu caracterul curent de
inserat (implicit “spaţiu”)
 Dacă mărimea valorii depăşeşte lăţimea minimă a
câmpului, câmpul va fi suprascris şi valorile nu vor fi
trunchiate
 Lăţimea câmpului trebuie stabilită pentru fiecare
operaţie de intrare sau ieşire, altfel se foloseşte
valoarea implicită 19
 Numărul de digiți utilizați pentru afișarea unei valori în
virgulă mobilă sau numărul cifrelor zecimale afișate se
poate stabili cu metoda precision():
int precision( ); // returneaza valoarea curenta
int precision(int p); // stabileste o noua valoare
// returneaza vechea valoare

 Şi în acest caz, precizia trebuie stabilită pentru


fiecare operaţie de intrare sau ieşire, altfel se
foloseşte valoarea implicită (6 digiți);
Observație:
- dacă se utilizează notația fixed sau scientific
parametrul funcției va reprezenta numărul de zecimale
- la notația implicită, parametrul va reprezenta numărul
total de digiți de afișare (înainte și după virgulă); 20
 Când un câmp trebuie completat, se foloseşte în mod
implicit caracterul “spaţiu”.
 Se poate specifica un caracter alternativ cu ajutorul
metodei fill( ):

char fill( ); // returneaza caracterul folosit pentru completare


char fill(char ch); // stabileste noul caracter pentru completare
// returneaza vechiul caracter

21
#include <iostream>
using namespace std;
int main(void){
cout.width(10);// latimea minima camp
cout<<"salut"<<'\n'; // implicit, alinierea se face la dreapta
cout.fill('%'); // stabilirea caracterului de completare
cout.width(10);
cout<<"salut"<<'\n'; // alinierea la dreapta
cout.setf(ios::left); // alinierea la stanga
cout.width(10);
cout<<"salut"<<'\n'; // alinierea se face la stanga
cout.width(10);
cout<<123.234567<< '\n'; // se utilizeaza formatul implicit
cout.width(10); // se stabileste latimea
cout.precision(3); // se stabileste precizia
22
cout<<123.234567<<'\n';
}
Clasa ios
Metodele de I/E de tip “manipulator”

 Manipulatorii permit formatarea operaţiilor de I/E


 Un manipulator este un identificator ce poate fi inserat
într-un flux de ieşire sau poate fi extras dintr-un flux de
intrare pentru a obţine un anumit efect (de exemplu
inserarea unei linii noi, etc.)
 Pentru a avea acces la manipulatorii care au parametri
trebuie să includem în programul nostru fişierul antet
iomanip.h (iomanip în Visual C++)

23
 Manipulatorii pot să apară în lanţul de operaţii de I/E
şi afectează doar stream-ul asociat, nu şi alte stream-
uri deschise în mod curent
 Principalul avantaj: sunt mai uşor de utilizat şi permit
scrierea mai compactă a codului
 Mulţi manipulatori se aseamănă cu flagurile din datele
membre de tip fmtflags ale clasei ios

24
Manipulator Scop Intrare/ Ieşire
dec Formatează datele numerice în zecimal Ieşire, intrare

endl Afişează un caracter de tip “newline” şi Ieşire


eliberează stream-ul
ends Afişează un caracter null Ieşire

flush Eliberează un stream Ieşire

hex Formatează datele numerice în hexazecimal Ieşire, intrare

oct Formatează datele numerice în octal Ieşire, intrare

resetiosflags (fmtflags f) Dezactivează indicatorii specificaţi în variabila f Intrare şi ieşire

setbase (int base) Stabileşte baza de numeraţie la “base” Ieşire

setfill (char ch) Stabileşte caracterul de completat dat de ch Ieşire

setioflags (fmtflags f) Activează indicatorii specificaţi în variabila f Intrare şi ieşire

setprecision (int p) Stabileşte numărul de cifre aflat după punctul Ieşire


zecimal sau numărul de digiți de afișare
25
ws Ignoră caracterele de tip “whitespace” Intrare

setw (int w) Stabileşte lăţimea câmpului la w Ieşire


# include <iostream>
#include <iomanip>

using namespace std;

int main(void)
{
cout << hex << 100 << endl;
cout << oct << 100 << endl;
cout << setfill( ‘X’)<< setw (10);
cout << 10 << endl << endl;
}

26
Exemplu:

#include <iostream>
#include<iomanip>
using namespace std;
int main(void) {
int a; bool b;
cout << "dati valoarea lui a in hexazecimal: ";
cin >> hex >> a;
cout << "\nValoarea lui a in zecimal: " << a << endl;

cin.setf(ios::boolalpha);
cout << "\ndati valoarea lui b: (true sau false) ";
cin >> b;
cout << "\nb ca valoare intreaga= " << b << endl;
27
}
Crearea propriilor funcţii de tip manipulator (de
tip utilizator)

 Manipulatorii sunt importanţi din două puncte de vedere:


 un manipulator poate grupa o secvenţă de mai multe
operaţii de I/E într-una singură
 un manipulator poate fi util când trebuie să realizăm
operaţii de I/E pe dispozitive care nu sunt standard

 Manipulatorii sunt de două tipuri:


 cei care operează cu stream-uri de intrare
 cei care sunt asociaţi stream-urilor de ieşire

28
 Forma generală a manipulatorilor de ieşire fără
parametri:
ostream& nume_manipulator (ostream& stream)
{
// cod
return stream;
}

Observație:
Deşi manipulatorul are ca parametru o referinţă a
stream-ului cu care operează, nu se utilizează nici un
argument când acesta este inserat într-o operaţie de
ieşire
29
 Sintaxa funcţiilor manipulator de intrare fără parametri
este următoarea:
istream& nume_manipulator (istream& stream)
{
// cod
return stream;
}

Observație: Este foarte important ca funcţiile proprii de tip


manipulator să returneze un stream:
 dacă nu se respectă această condiţie, ele nu pot fi
utilizate în operaţii de intrare şi ieşire înlănţuite

30
# include <iostream>
using namespace std;

ostream& init(ostream& stream)


{
stream.width(10);
stream.precision(4);
stream.fill(‘*’);
return stream;
}

int main(void)
{
cout<< init << 123.123456 << endl << endl;

}
31
Verificarea stării sistemului de I/E

 Sistemul de I/E al limbajului C++ conţine informaţii de


stare cu privire la operaţiile de I/E
 Starea curentă a sistemului de I/E este conţinută într-o
variabilă de tip iostate
Nume Semnificaţie

goodbit Are valoare 1 dacă nu există erori


Are valoare 0 dacă se semnalează o eroare

eofbit Are valoare 1 când se întâlneşte EOF


Are valoare 0 în caz contrar

failbit Are valoare 1 când s-a produs o eroare legată de


logica internă a operației; în caz contrar are valoare 0

badbit Are valoare 1 în cazul eșuării operației de 32


citire din/scriere în flux şi 0 în caz contrar
 Există două moduri prin care putem obţine informaţii
despre starea sistemului:
1. în primul caz, se poate apela metoda membră
rdstate( );
 Metoda returnează starea curentă a indicatorilor de eroare,
care sunt codificaţi într-o variabilă de tip iostate
2. în al doilea caz putem utiliza una din metodele:
int bad( ), int eof( ), int fail( ), int good( )
 metodele bad( ) şi fail( ) returnează o valoare nenulă dacă
bitul badbit, respectiv failbit este activat
 metoda good( ) returnează valoare nenulă, dacă nu se
produce nici o eroare
 metoda eof( ) returnează o valoare nenulă, dacă s-a atins
sfârşitul de fişier

33
Exemplul 1:
if(! (iosObject.rdstate() & ios::failbit))
{
//ultima operatie nu a esuat
}

Exemplul 2:
while (!in.eof())
{
in.get (car) ;
// verifică eroarea
if (! in.good() && !in.eof() ) {
cout << ”Eroare de I/E …..se termina programul\n” ;
return 1;
}
34
}

 Obiectele stream (flux) pot fi testate direct folosind
operatori logici, in felul acesta se testează flagul ios::good
asociat fluxului

if (cin)
// cin este interpretat ca fiind de tipul bool
if (cin >> x)
// cin interpretat ca fiind de tip bool, după operația de
extracție

 Dacă a apărut o eroare, flagurile de eroare asociate


fluxului pot fi șterse ulterior cu metoda ios::clear()

35
Clasa ostream

 Această clasă permite formatarea și scrierea datelor ca


secvențe de caractere (într-un obiect de tipul streambuf
– buffer asociat streamului de ieșire)

 Operațiile de ieșire pot fi:


- neformatate: prin metode membre ale clasei ostream,
care pot scrie un număr determinat de caractere în
secvența de ieșire
- formatate: inserarea datelor formatate în flux se poate
face folosind operatorul de inserție <<, care poate fi
supraîncărcat pe clase utilizator pentru scrierea de
obiecte în fluxul de ieșire

36
 Principalele metode membre:
 ostream(streambuf *);
 ostream& put(char);
 ostream& write(const char*, int n);
 ostream& flush( ); // goleste zona tampon asociata
 long tellp( ); // returneaza pozitia curenta in flux
 ostream& seekp(long, seekdir = ios::beg);
unde pentru seekdir se poate asocia ios::{beg, cur, end}

37
Clasa istream
 Această clasă permite citirea și interpretarea intrării din
secvențe de caractere

 Permite operații de intrare neformatate (nu se face nici o


interpretare a caracterelor preluate de la intrare) –
majoritatea metodelor membre preiau un număr de
caractere din secvența de intrare sau informații despre
ultima operație de intrare neformatată

 Datele formatate pot fi extrase din fluxul de intrare,


folosind operatorul de extracție >>, care poate fi
supraîncărcat pe clase utilizator, pemițând astfel și
citirea de obiecte
38
 Principalele metode membre :
 istream(streambuf*);
 int get( ); // extrage urmatorul caracter (inclusiv EOF)
 istream& get(char& c ); // extrage urmatorul caracter si il
depune în c
 istream& get( char* s, streamsize n, char delim=‘\n’ );
// extrage si depune n caractere sau pana
la intalnirea caracterului specificat și le
depune la adresa data
 istream& get(streambuf&, char = '\n'); //inserează în
buffer caracterele extrase
 int peek( ); // returneaza urmatorul caracter
din flux fara a-l extrage
 istream& putback(char c); // pune inapoi in flux caracterul c
 istream& read(char*, streamsize n); // extrage pana la
n caractere, dar se opreste pentru EOF

39
 istream& getline(char*, streamsize n, char = '\n');
extrage si depune la adresa data cel mult n-1 caractere; extragerea
se opreste la intalnirea caracterului specificat sau la intalnirea EOF;
se pune delimitatorul de sir (‘\0’) la coada;
 int gcount( ); // returneaza numarul de caractere extras
ultima data de functia read() sau getline()
 istream& ignore(int n = 1, int = EOF); // sare peste n
caractere, dar le extrage din flux; se opreste
la intalnirea caracterului specificat;
 long tellg( ); // returneaza pozitia curenta in flux
 istream& seekg(long, seek_dir=ios::cur); // muta
pozitia curenta in flux, in raport cu pozitia de
inceput, de sfarsit sau in raport cu pozitia curenta

40
Funcţiile de tip “inserter” şi “extractor”
 Sistemul de I/E din C++ permite supraîncărcarea
operatorilor de I/E
 Operaţia de ieşire se numeşte inserţie (insertion), iar
operatorul (<<) este denumit operator de inserţie (insertion
operator):
 această denumire provine de la faptul că un operator de ieşire
inserează informaţia în stream

 Operaţia de intrare se numeşte extracţie (extraction), iar


operatorul (>>) este denumit operator de extracţie
(extraction operator):
 această denumire provine de la faptul că un operator de intrare
extrage informaţia din stream
41
Supraîncărcarea operatorului de ieşire

 Forma generală a funcţiilor inserter:


ostream& operator << (ostream& stream, Tip ob)
{
// corp inserter
return stream;
}

 Primul parametru este o referinţă a unui obiect de tip


ostream pentru că obiectul va fi inserat într-un stream
de ieșire
 Al doilea parametru primeşte obiectul care trebuie
inserat în fluxul de ieșire 42
 Inserter-ul returnează o referinţă de tip ostream:
 acest lucru este cerut pentru utilizarea inserter-ului în
expresii complexe (pentru a putea face operaţii de inserţie
înlănţuite) :
cout << OB1 << OB2 << OB3 ;

Obs: Un inserter nu poate fi o metoda membră a clasei în


care operează (deoarece primul parametru este o referinţă la
clasa ostream), dar poate fi funcţie friend sau externă clasei
(functie globala)

43
Supraîncărcarea operatorului de intrare
 Forma generală a funcţiilor extractor:
istream& operator>> (istream& stream, Tip &ob)
{
// corp extractor
return stream;
}

 Extractorii returnează o referinţă a unui istream, care


este un stream de intrare
 Primul parametru trebuie să fie o referinţă a unui stream
de intrare
 Al doilea parametru este o referinţă a obiectului care
primeşte datele de intrare deoarece modifică obiectul
primit ca parametru
 Un extractor nu poate fi metoda membră a unei clase,
doar funcţie friend sau funcție externă clasei (globala)44
Exemplu: supraincarcare operatori << si >> cu functii friend

# include <iostream>
using namespace std;

class Coord {
int x, y;
public:
Coord( ) {
x=0; y=0;
}
Coord(int i, int j) {
x=i; y=j;
}
friend ostream &operator<< (ostream& , Coord ob);
friend istream &operator>> (istream& , Coord &ob);
};
45
// inserter pentru clasa Coord
ostream& operator<<(ostream& stream, Coord ob)
{
stream << "abscisa: " << ob.x << ", ordonata: " << ob.y
<< '\n';
return stream;
}

// extractor pentru clasa Coord


istream& operator>>(istream& stream, Coord& ob)
{
cout<< endl << "\tIntroduceti coordonatele: "<< endl;
cout<< "\t abscisa : ";
stream >> ob.x;
cout<< "\t ordonata : ";
stream >> ob.y;
return stream; 46

}
// functia main
int main (void){
Coord a(1,1), b(10,24);
cout << "Obiecte cu coordonate predefinite: " <<endl;
cout<< "\tObiect1: "<<a << "\tObiect2: "<<b;
cout<< "\nCoordonate noi: ";
cin >> a;
cout << "Afisare coordonate noi:"<<endl<<'\t';
cout << a;

47
Exemplu: supraincarcare operatori << si >> cu functii externe (nu sunt friend)

# include <iostream>
using namespace std;

class Coord {
int x, y;
public:
Coord() {
x = 0; y = 0;
}
Coord(int i, int j) {
x = i; y = j;
}
int getX() { return x; }
int getY() { return y; }
void setX(int x) {
this->x = 0;
}
void setY(int y) {
this->y = 0; 48
}
};
// inserter pentru clasa Coord
ostream& operator<<(ostream& stream, Coord ob){
stream << "abscisa: " << ob.getX()<< ", ordonata: " << ob.getY() << '\n’;
return stream;
}

// extractor pentru clasa Coord


istream& operator>>(istream& stream, Coord& ob){
int x, y;
cout << endl << "\tIntroduceti coordonatele: " << endl;
cout << "\t abscisa : ";
stream >> x;
ob.setX(x);
cout << "\t ordonata : ";
stream >> y;
ob.setY(y);
49
return stream;
}
// functia main – ramane la fel ca in varianta cu functii friend
int main(void) {
Coord a(1, 1), b(10, 24);
cout << "Obiecte cu coordonate predefinite: " << endl;
cout << "\tObiect1: " << a << "\tObiect2: " << b;
cout << "\nCoordonate noi: ";
cin >> a;
cout << "Afisare coordonate noi:" << endl << '\t';
cout << a;

50
 În C++ toate stream-urile sunt identice, de aceea un
operator de tip inserter (de exemplu) supraîncărcat,
poate fi folosit pentru a trimite rezultatele pe ecran sau
într-un fişier

 Supraîncărcarea operatorilor de I/E oferă simplitate şi


elimină necesitatea memorării denumirilor unor
funcţii/metode de I/E supraîncărcate pentru fiecare tip
de date

51
PROGRAMAREA
CALCULATOARELOR – ALGORITMI
MODUL 2
FISIERE
1

CURS 10
OPERAŢII DE I/E C++ CU FIŞIERE
 Operațiile de I/E C++ cu fișiere implică operații cu
fluxurile asociate fișierelor
 Pentru a realiza operaţii de I/E C++ cu fişiere, trebuie
să includem în program fişierul antet fstream.h (sau
fstream), care defineşte mai multe clase pentru
definirea fluxurilor pentru lucrul cu fișiere:
 ifstream, ofstream şi fstream

 Aceste clase sunt derivate din clasele istream şi


ostream

2
!!! În C++, un fişier este deschis prin cuplarea lui la un
stream
 Înainte de deschiderea fişierului trebuie să obţinem
mai întâi un stream
 Pentru a crea un stream de intrare, el trebuie
declarat ca fiind de tip ifstream, iar pentru a crea unul
de ieşire, streamul trebuie declarat de tip ofstream
 Streamurile care realizează ambele tipuri de operaţii
trebuie declarate de tip fstream
 Exemplu:
ifstream in;
ofstream out;
fstream io;
3
Flux de intrare
(ifstream)
Fișier Program

Flux de iesire
(ofstream)
Fișier Program

Flux bidirectional
(fstream)
Fișier Program

4
Deschiderea/închiderea unui fişier

 Metoda open( ) este folosită pentru a asocia un fişier


unui stream
 Această metodă este membră a tuturor celor trei
clase de tip stream şi are prototipul:
void open(char *nume_fişier, ios_base:: openmod mod, [int
acces=ios::_Openprot] );
 primul parametru reprezintă un pointer la numele fişierului
 parametrul mod specifică modul de deschidere a fişierului
 Al treilea parametru se referă la modul de acces la fișier care
de obicei e de forma ios::_Openprot, modul implicit de
protectie la deschiderea fisierelor (optional).

5
 Lista valorilor posibile pentru al doilea parametru
este definită în clasa “ios_base”, prin membri publici
de tip openmode (mască de biți):

in permite operații de intrare


out permite operații de ieşire
ate după deschidere, poziţia curentă va fi chiar sfârşitul
fişierului
app deschis pentru adăugare (scriere la sfârşit de fişier)
trunc dacă fişierul există îi şterge conţinutul (opţiune
implicită în caz că e vorba de un fişier de ieşire şi nu
apare una dintre opţiunile “ios::app” sau “ios::ate”)
binary deschidere în mod binar

6
 Exemplu:
ofstream out;
ofstream out("test", ios::out);
out.open("test", ios::out);

Sunt permise înlănţuiri ale specificatorilor modului de deschidere


prin “SAU LOGIC” la nivel de bit:
ifstream f1(" nume ", ios::in | ios::binary);

 Parametrul mod are valori implicite:


 pentru un obiect de tip ifstream valoarea implicită pentru
parametrul mod este ios::in
 pentru un obiect de tip ofstream este ios::out | ios::trunc
 prin urmare sintaxa pentru instrucţiunea de mai sus se
poate simplifica:
out.open("test"); 7
În consecință codul pentru deschiderea unui fișier este:
ofstream out;
out.open(" test ", ios::out| ios:trunc, ios::_Openprot);
sau cu o sintaxă simplificată:
out.open(" test");

sau
ofstream out("test"); // open nu e obligatoriu

 Putem generaliza:
ifstream fin ("test"); // in file
ofstream fout ("test"); // out file
fstream finout ("c:\\Test\\test"); // in-out file 8
 Observație! Dacă operaţia de deschidere a fişierului
eşuează, stream-ul va avea valoarea zero și este setat
flagul failbit pentru streamul respectiv.
 Înainte de utilizarea unui fişier trebuie să verificăm dacă
operaţia de deschidere a reuşit, folosind o secvenţă de
genul:

if (!stream_propriu) {
cout<< " Nu poate deschide fişierul\n ";
// trateaza eroarea}

sau
if ((stream_propriu.rdstate() & stream_propriu.failbit)) {
cout<< "Nu poate deschide fisierul \n" ;
// tratează eroarea
9
}
 Putem folosi metoda is_open () pentru a testa dacă un
fișier a fost conectat la un stream:
bool is_open( ); // true deschidere reusita
// false deschidere esuata

 Pentru a închide un fişier cuplat la un stream se


foloseşte metoda close():
stream_propriu.close( );

 După ce am deschis un fişier, este foarte uşor să


citim/scriem date din/în acesta

10
Citirea/scrierea datelor în fişiere

 Pentru operații de intrare/ieșire formatate se utilizează


operatorii stream (<<) şi (>>) într-un mod similar cu cel
utilizat la operaţiile de I/E cu consola, cu excepţia
faptului că în loc de cin şi cout se foloseşte un stream
cuplat la fişier (ex.1)

 Pentru operații neformatate se utilizează metodele


membre pentru operații de acest tip moștenite din
clasele istream și ostream (ex.2, 3)

11
I/E PENTRU FIȘIERE BINARE

 C++ oferă pentru fișierele binare deschise cu


ios::binary metodele binare, get() și put().

 Cu metoda membră put() scriem un byte și cu metoda


get() citim un byte.
Prototipul lui get() este:
istream& get(char& car);

 Metoda citește din streamul asociat un caracter și


plasează valoarea în variabila car. Metoda returnează o
referință către streamul de intrare.
12
 Prototipul metodei put() este:
ostream& put(char car);
- metoda scrie caracterul car în stream și returnează o
referință către streamul de ieșire.

 Pentru a citi un string putem folosi metoda getline(), cu


următoarea sintaxă:
istream& getline(char* sir, int nr_max, char delim=’\n’);

- va citi date până la delim sau nrmax-1 caractere. Este


potrivită pentru citirea liniilor din fișierele text.

13
Pentru a citi/scrie blocuri de date binare, se folosesc
metodele read()/write(), cu prototipurile:

istream& read(unsigned char *buf, int number);


ostream& write(const unsigned char *buf, int number);

 Metoda read (), citește number de bytes în stream și îi


pune în buffer-ul asociat cu pointerul buf.
Metoda write (), scrie din zona buffer, pointată de buf,
un number de bytes în stream-ul asociat.

 pot fi folosite și alte metode prezentate la operațiile de


I/E.
14
Exemplul 1: (scriere/citire tipuri predefinite in/din fisier folosind
operatorul de insertie/extractie)

#include <iostream>
#include <fstream>
using namespace std;
int main(void){
ofstream fout ("test"); // creaza un fisier de iesire
if (!fout) {
cout<<"Nu se poate deschide fisierul de iesire\n";
exit(0);
}
fout<<"Salut!\n";
fout<<100<<" "<<hex<<100<<endl;
15
fout.close( );
ifstream fin("test"); // deschide un fisier de intrare
if (!fin) {
cout<<"Nu se poate deschide fisierul de intrare\n";
exit(0);
}
char sir[80];
int i;
fin>>sir>>i;
cout<<sir<< " "<<i<<endl;
fin.close( );
}

16
Exemplul 2: ( scriere cu write(), citire cu read() si getline() )

#include <iostream>
using namespace std;
#include <fstream>

int main(void){
int numar=12;
ofstream out ("test.txt"); // creaza un fisier de iesire
if (!out) {
cout<<"Nu se poate deschide fisierul de iesire\n";
return 1;
}
out.write("Salut !\n", 8);
out.write((char *)&numar, sizeof(int)); // simbolul pt Form Feed
out.close( ); 17
ifstream in ("test.txt");
if ( !in ) {
cout<< "Nu poate deschide fişierul de intrare\n" ;
return 1;
}

char sir[20];
in.getline(sir, 19);
in.read ((char*) &numar, sizeof(int));
cout << sir << " " << numar << endl;
in.close( ) ;
return 0;
}

18
//Exemplu scriere sir de caractere, valori float si int separate prin spatii
#include <iostream>
#include <fstream>
using namespace std;
#include <stdlib.h>

int main(){
ofstream out("test.txt"); // deschidere fisier pentru operatii de iesire
if(!out) {
cout << "Fisierul test.txt nu poate fi deschis.\n";
exit(1);
}

out << "R " << 9.9 <<" "<< 10<< endl;
out << "T " << 9.9 <<" "<< 9 << endl;
out << "M " << 4.8 <<" "<< 4 <<endl;
out.close(); 19
ifstream in("test.txt"); // flux de intrare pentru fisierul test.txt
if(!in) {
cout << " Fisierul test.txt nu poate fi deschis.\n";
exit(1);
}

char item[20];
float cost;
int mark;

in >> item >> cost >>mark;


cout << item << " " << cost <<" "<< mark<< "\n";
in >> item >> cost >>mark;
cout << item << " " << cost <<" "<< mark<< "\n";
in >> item >> cost >>mark;
cout << item << " " << cost <<" "<< mark<< "\n";
in.close(); 20

}
//Acelasi exemplu, citire pana la EOF
#include <iostream>
#include <fstream>
using namespace std;
#include <stdlib.h>

int main(){
ofstream out("test.txt"); // deschidere fisier pentru operatii de scriere
if (!out) {
cout << "Fisierul nu poate fi deschis.\n";
exit(1);
}
out << "R " << 9.9 << " " << 10 << endl;
out << "T " << 9.9 << " " << 9 << endl;
out << "M " << 4.8 << " " << 4 << endl;
//out<<std::ifstream::traits_type::eof(); //scriere explicita EOF 21

out.close();
ifstream in("test.txt"); // deschidere fisier pentru operatii de intrare
if (!in) {
cout << "Eroare deschidere fisier test.txt .\n";
exit(1);
}
char item[20];
float cost;
int mark;

while(1){
in >> item >> cost >>mark;
if(!in.eof())
cout << item << " " << cost <<" "<< mark<< "\n";
else
break;
22
}
//SAU
/*
int eof_m;
while (1){
in >> item >> cost >> mark;
eof_m = in.peek(); // citeste caracter din flux fara sa-l extraga
if (eof_m == -1) break;
cout << item << " " << cost << " " << mark << "\n";
};
*/
in.close();
}

23
Exemplul 3 ( utilizare getline() ):

#include <iostream>
using namespace std;
#include <fstream>

int main( ) {
char infname[30];
char outfname[30];
char buffer[101];
cout << "Numele fisierului sursa ";
cin >> infname;
ifstream in(infname);
if (!in) {
cout << "Nu s-a deschis fisierul " << infname << endl;
exit(0);
24
}
cout << "Fisierul destinatie: ";
cin >> outfname;
ofstream out(outfname);
if (!out) {
cout << "Nu s-a putut deschide fisierul " << outfname <<endl;
exit(0);
}

while (!in.eof( )) {
in.getline(buffer,100);
out << buffer << endl; // scriere in fisierul destinatie
}
in.close( ); // inchidere fisier sursa
out.close( ); // inchidere fisier destinatie
}

25
EXEMPLE METODA READ() :
// program ce utilizeaza metoda read() pentru a citi date dintr-un fisier
// care se creaza in prealabil
#include <iostream>
#include <fstream>
#include <stdlib.h>
using namespace std;
int main(){
char sir[11]=" ";
fstream io ;
io.open("test.txt", ios::out);
if (!io) {
cout<< "Nu poate deschide fisierul de scriere\n" ;
exit(1);}
io.write("Sir de test de verificat", 15);
26
io<< "abcdefghijk";
io.close();
io.open("test.txt", ios::in);
if (!io) {
cout<< "Nu poate deschide fisierul de citire\n" ;
exit(1);
}
while(1){
io.read(sir,10) ;
if (!io.eof())
cout<<sir ;
else break;
}
io.close() ;

}//main
27
FIȘIERE ÎN ACCES ALEATOR

 În cadrul sistemului de I/E în C++, avem acces în mod


direct aleator, folosind metodele seekg() și seekp(). Cele
mai utilizate forme ale metodelor sunt:
istream& seekg (streamoff offset, seek_dir origin) ;
ostream& seekp (streamoff offset, seek_dir origin) ;

 offset - valoarea offsetului relativ la poziția stabilită prin


parametrul origin (de tip streamoff, e definit în iostream)
 origin poate avea una dintre valorile constante definite
ca membrii publici în clasa ios_base: beg, cur, end (de
tip seek_dir).
28
EXEMPLE RANDOM FILES:
/* Programul descris in exemplul urmator va ilustra lucrul cu metoda
seekp(). Ne va permite să schimbăm un anumit caracter dintr-un
fişier; este necesară specificarea în linia de comandă a unui nume,
urmat de poziţia caracterului pe care dorim să-l schimbăm şi de noul
caracter. Fişierul va fi deschis pentru operaţiile de citire/scriere.*/

#include <iostream>
#include <fstream>
#include <stdlib.h>
using namespace std;

int main( int argc, char*argv[] ){


if(argc!=4){
cout << "Utilizare: SCHIMBA < nume_fisier> <octet> <car>\n" ;
exit(1) ; 29

}
fstream out;
out.open(argv[1], ios::in | ios::out | ios::binary) ;
if(!out){
cout<<"Nu poate deschide fisierul";
exit(1);
}
out.seekp(atoi(argv[2]),ios::beg) ;
out.put(*argv[3] ) ;
out.close() ;
cin.get();
}//main

30
Exemplu pentru operatii de scriere/citire obiecte in/din fisiere prin
supraincarcarea operatorilor >> respectiv << si acces aleator

#include <iostream>
#include <fstream>
using namespace std;

class Coord {
int x, y;
public:
Coord( ) { x=0; y=0; }
Coord(int i, int j) { x=i; y=j; }
friend ostream &operator<< (ostream& , Coord ob);
friend istream &operator>> (istream& , Coord &ob);
}; 31
// inserter pentru clasa Coord
ostream& operator<<(ostream& stream, Coord ob)
{
stream << ob.x << " " << ob.y << '\n';
return stream;
}

// extractor pentru clasa Coord


istream& operator>>(istream& stream, Coord& ob)
{
stream >> ob.x>>ob.y;
return stream;
} 32
// functia main
int main (void){
Coord a(1,1), b(10,24);
cout << "Obiecte cu coordonate predefinite: " <<endl;
cout<< "\tObiect1: "<<a << "\tObiect2: "<<b;
cout<< endl << "\tIntroduceti noile coordonate: ";
cin >> a; // citire obiect de la tastatura
cout << "Afisare coordonate noi:"<<endl<<'\t';
cout << a; // afisare obiect pe ecran

fstream fio("test.txt", ios::out|ios::in); // deschidere fisier

33
if(!fio){
cout<<"\n Deschiderea fisierului a esuat";
exit(0);
}
cout<<"\nScriem obiectul in fisier!\n";
fio<<a; // scriere obiect in fisier

fio.seekg(0, ios::beg); // repozitionare la inceput de fisier


Coord c;
fio>>c; // citire obiect din fisier
cout<<"\nCoordonatele obiectului citit din fisier: "<<c;
fio.close();
// cin.ignore();
// cin.get();
}
34
FISIERE CU CLASE – PROBLEMA LABORATOR
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <conio.h>
#include <fstream>
using namespace std;
class Fractie {
int a, b;
public: Fractie();
Fractie(int j, int k);
void setA(int x) { a = x; }
void setB(int y) { b = y; }
int getA() { return a; }
int getB() { return b; }
friend Fractie operator+ (Fractie x, Fractie y);
friend Fractie operator- (Fractie x, Fractie y);
friend Fractie operator* (Fractie x, Fractie y);
friend Fractie operator/ (Fractie x, Fractie y);
friend ostream& operator<< (ostream&stream, Fractie ob);
friend istream& operator>> (istream&stream, Fractie &ob); 35
void simplify();
};//Fractie
Fractie::Fractie( ){
a = 0;
b = 1;}

Fractie::Fractie(int j, int k){


a = j;
if (k != 0) b = k;
else { k=1; cout << "\nNumitorul nu poate fi 0!!!"; }
}

Fractie operator+ (Fractie x, Fractie y){


Fractie s;
if (x.b == y.b){
s.a = y.a + x.a;
s.b = y.b;}
else {
s.a = (y.b*x.a) + (x.b*y.a);
s.b = (x.b*y.b);}
s.simplify();
return s;} 36
Fractie operator- (Fractie x, Fractie y) {
Fractie s;
if (x.b == y.b) {
s.a = y.a - x.a;
s.b = y.b; }
else {
s.a = (y.b*x.a) - (x.b*y.a);
s.b = (x.b*y.b); }
s.simplify();
return s;}
Fractie operator* (Fractie x, Fractie y) {
Fractie s;
s.a = x.a*y.a;
s.b = x.b*y.b;
s.simplify();
return s;
}
Fractie operator/ (Fractie x, Fractie y) {
Fractie s;
s.a = x.a *y.b;
s.b = x.b * y.a; 37
s.simplify();
return s;
}
istream& operator >> (istream &stream, Fractie &ob) {
cout << "Cititi numaratorul: ";
stream >> ob.a;
cout << "Cititi numitorul: ";
stream >> ob.b;
return stream;
}
ostream& operator<< (ostream &stream, Fractie ob) {
stream <<" "<< " Numaratorul este: " << ob.getA();
stream << "Numitorul este: " << ob.getB();
return stream;
}
void Fractie::simplify(){
int c = abs(a);
int d = abs(b);
while (c != d) {
if (c > d) c = c - d;
else d = d - c; }
a = a / c;
b = b / c; }
38
int main(){
Fractie ob1, ob2,o3, o4, o5, o6;
cin >> ob1;
cin >> ob2;
o3= ob1+ob2;
o4 = ob1-ob2;
o5= ob1*ob2;
o6 = ob1/ob2;
cout <<"Obiectele initiale "<<endl<<"Ob1: "<< ob1<<endl<<"Ob2: "<<ob2;
cout << endl<<"Suma lor: " << o3;
cout << endl<<"Diferenta lor: " << o4;
cout << endl<<"Produsul lor: " << o5;
cout << endl<<"Catul lor: (impartire intreaga)" << o6;
ofstream fout;
fout.open("fractie.txt", ios::out);
fout <<"Obiectele initiale "<<endl<<"Ob1: "<< ob1<<endl<<"Ob2: "<<ob2;
fout << endl<<"Suma lor: " << o3;
fout << endl<<"Diferenta lor: " << o4;
fout << endl<<"Produsul lor: " << o5;
fout << endl<<"Catul lor: (impartire intreaga)" << o6;
fout.close();
cout <<"\n Datele au fost scrise in fisierul: fractie.txt\n";
// cin.ignore(); cin.get(); 39
}//main
Operaţii de I/E pe şiruri de caractere

40
 Fişierul antet strstream.h (strstream, sstream)
conţine definiţiile claselor ostrstream (ostringstream),
istrstream (istringstream), strstream (stringstream)
ce implementează operaţii de I/E pe tablouri de
caractere

 Inserarea sau extracţia pe aceste fluxuri se face prin


tablouri de caractere (dinamice sau statice)

41
 Clasele specificate sunt similare cu cele destinate
lucrului pe fişiere, fiind derivate din clasele iostream
corespondente şi ca urmare moştenesc metodele
membre ale acestora

 De exemplu, pentru operaţii de intrare se pot folosi


metodele membre ale clasei istream (get( ), read( ),
getline( )), iar pentru operaţii de ieşire se pot folosi
metodele clasei ostream (put( ), write( ))

42
 Clasa ostrstream:
Controlează inserarea de elemente și codificarea
obiectelor într-un buffer (de tip strstreambuf)
 ostrstream (void); // crează un obiect ostrstream cu
zonă tampon alocată dinamic
 ostrstream (char *buf, int size, int mode = ios::out);
// utilizatorul specifică zona tampon
şi dimensiunea acestuia
 streamsize pcount (void); // dă numărul de octeţi
memoraţi în buffer pentru un flux de ieşire
 char* str (void); // blochează şi returnează buffer-ul
asociat fluxului; dacă acesta a
fost alocat dinamic, trebuie dealocat explicit

43
Exemplu:
ostrstream odyn; // zona tampon alocata dinamic
char *buf = odyn.str( );

delete buf;
char buffer[1024];
ostrstream ssta(buffer, 1024);

44
#include <strstream>
#include <iostream>
using namespace std;

int main(){
char str[80];
ostrstream outs(str, sizeof(str));
outs << 125 << " " << 17.25 <<" astea sunt numerele";
outs << ends; // terminatorul null
cout <<"Numarul de caractere="<< outs.pcount()<<endl;
// afiseaza numarul de caractere din outs
cout << str<<endl;

} 45
 Clasa istrstream:
 istrstream (const char *); // crează un obiect istrstream
ce utilizează un şir dat
 istrstream (const char *, int n); // crează un obiect
istrstream ce utilizează primii n
octeţi dintr-un şir dat (mai sigură !)
 strstreambuf *rdbuf( ) const; // returnează un pointer la
obiectul strstreambuf asociat streamului
 Exemplu:
char data[128];
//...
istrstream istr(data, 128);
sau
istrstream istr(data);

46
#include <iostream>
#include <strstream>
using namespace std;

int main() {
istrstream s("15 10.75 Test pentru istrstream ");
int i;
float f;
char buf[100];
s >> i >> f>> buf; // intrarile sunt delimitate prin spatii albe
cout << "i = " << i << ", f = " << f<<", buf = " << buf << endl;
cout << s.rdbuf(); // afisam restul din obiectul istrstream

} 47
 Clasa strstream:

Controlează inserarea și extragerea de elemente și


codificarea obiectelor utilizând un obiect de strstreambuf

 strstream (void); // ca şi la ostrstream


 strstream (char *buf, int size, int mode); // ca şi la
ostrstream
 char* str (void); // blochează şi returnează buferul
asociat fluxului; dacă acesta a fost alocat dinamic,
trebuie dealocat explicit

48
#include <iostream>
#include <strstream>
using namespace std;

int main() {
strstream s;
int i; float f; char sir[20];
s<<"8.75 2111 este media la programare"<<ends;
s>>f>>i>>sir;
cout<<f<<" "<<sir<<" "<<s.str()<<" a grupei "<<i<<endl;
// cin.get();
}

49
PROGRAMAREA CALCULATOARELOR –
ALGORITMI
MODUL 3

CURS 11
CUPRINS
 Introducere în generice C/C++
 Funcţii şi metode template

 Supraîncărcarea explicită a unei funcţii template

 Parametrii funcţiilor template

 Restricţiile funcţiilor template

 Clase template

2
INTRODUCERE ÎN GENERICE C/C++

În C ++ → template-urile - funcţii sau clase care sunt scrise


pentru mai multe tipuri de date, tipuri nespecificate la
declarare
Tipurile de date sunt specificate ca parametri → clase sau
funcţii generale, modul de lucru este determinat de natura
datelor cu care vor opera.
Avantaje:
- Definirea unui mod de lucru cu tipuri de date generice,
orice tip de dată transmis ca parametru, fiind o
personalizare rezolvată de către compilator.

3
FUNCŢII ŞI METODE TEMPLATE

 Terminologie: Funcţii template – nu fac parte din clase,


metode template – funcţii membre claselor
 Definesc un set general de operaţii ce vor fi aplicate unui
set de date de intrare. Tipul datelor va fi transmis funcţiilor
ca parametri ai template-ului
 Este eliminat procesul de supraîncărcare
 “Template” – termenul sugerează faptul că se oferă doar
un model pentru clase / funcţii, ce descrie funcţionalitatea,
lăsând compilatorul să completeze detaliile necesare.

4
SINTAXA
template <class tipg1[,…class tipgi]> tip_ret numeFuncT (lista_par){
……………..
instructiuni
}
Sau, mai general:

template <typename tipg1[,…typename tipgi]> tip_ret numeFuncT


(lista_par){
……………..
instructiuni
}
tipgi – un nume general pentru tipul de date folosit de funcţie
5
APELUL FUNCŢIILOR TEMPLATE

 Când funcţia este apelată pentru un anumit tip de date,


tipgi va fi înlocuit automat de către compilator cu tipul
specific datelor utilizate
- tipgi se numeşte parametrul template-ului
- template <typename tipg1[,…typename tipgi]>, este antetul
template-ului

Apelul unei funcţii template:


numeFuncT(lista_param_efectivi);

Tipul parametrilor efectivi va fi analizat de compilator pentru


a genera funcţia.
6
Exemple:
// Generice cu un singur tip

#include <iostream>
using namespace std;

template <typename T> void F(T x);


int main(){
F(1020) ; // int
F('T') ; // char
F(10.20) ; // double
F("Salut") ; // char *

return 0;
}

template <typename T> void F(T x){


cout <<x << '\n' ;
7
}
// COMPARA 2 VALORI NUMERICE GENERICE
#include <iostream>
using namespace std;
template <class X> void compara(X a, X b);
int main(void){
int x, y;
float m, n;
cout << "\nPrimul numar intreg: ";
cin >> x;
cout << "\n al doilea numar intreg: ";
cin >> y;
cout << "\nPrimul numar flotant: ";
cin >> m;
cout << "\n al doilea numar flotant: ";
cin >> n;
compara(x,y);
compara(m,n);
return 0; } //main 8
template <class X> void compara(X a, X b){
if(a>b) cout << "\nValoarea "<<a<<" este mai mare";
else cout << "\nValoarea "<<b<<" este mai mare sau
egala";
}

Apelul:
compara(x, m);
este corect?

9
// MAXIMUL DINTRE 2 VALORI GENERICE DE ACELASI TIP

#include <iostream>
using namespace std;
#include <string>
template <typename T> T mmax(T x, T y) ;

int main(void){
int i=7,j=9 ;
string s1 ="Pg";
string s2 = "Gen";
// CName a, b ;
cout <<"\n Rezultate maxim tipuri generice\n";
int k= mmax(i,j) ;
cout <<k <<endl;
cout << mmax('A', 'B')<<endl;
cout << mmax(s1, s2) << endl;
10
//cout << mmax(10, 20.5) ; Err tipuri diferite
//CName c=mmax(a,b); //se va supraincarca mmax
// separat pentru obiecte, fara op. ?:
return 0; } //main

<typename T> T mmax(T x, T y){


return ((x>y) ?(x) :(y) );
}

11
// ARGUMENTE DEDUSE IMPLICIT SAU SPECIFICATE EXPLICIT
#include <iostream>
using namespace std;
template <class T> T suma(T x, T y) ;
void main(void){
cout<<"\n Argumente deduse implicit sau specificate explicit\n";
cout<< suma(10,20)<<" int implicit"<<endl; //int
cout<< suma(10.0, 20.0)<<" double implicit"<<endl; //double
// suma (10, 20.0); //Eroare, tipuri diferite
cout<< suma <int> (10,20.0)<<" int explicit"<< endl; // Ok, int, specificare
// explicita
cout<<suma <double> (10, 20.0)<<" double explicit"<<endl; //Ok, double

} //main
template <class T> T suma(T x, T y){
return x+y;
}
Concluzie: Compilatorul poate crea atâtea versiuni pentru funcţie, câte12
sunt necesare pentru tratarea tipurilor utilizate.
FUNCŢII CU MAI MULTE TIPURI GENERICE
 In declaraţia unei funcţii template putem avea mai multe tipuri
generice, separate prin virgulă. Compilatorul va crea practic
atâtea versiuni ale funcţiei câte îi sunt necesare, fiind
considerat un proces de supraîncărcare implicită.
#include <iostream>
using namespace std;
template <class X, class Y> void afis(X a, Y b);
int main(void){
afis(12, "abcd");
afis(12.8, 'c');

return 0;
}//main
template <class X, class Y> void afis(X a, Y b){
cout << "\n Prima valoare: "<<a;
cout << "\n A doua valoare: "<<b;
13
}
SUPRAÎNCĂRCAREA EXPLICITĂ A UNEI FUNCŢII TEMPLATE

 Chiar dacă funcţiile template creează o supraîncărcare prin


natura lor, ele pot fi supraîncărcate şi explicit. Efectul
supraîncărcării explicite este că pentru tipul de date pentru
care a fost făcută supraîncărcarea, programul va urma
varianta dată de această funcţie.
 Varianta de supraîncărcare realizată în antetul funcţiei
template este numită specializare explicită a funcţiei
template.
 Dacă numărul de parametri în cazul supraîncărcării este
egal cu cel al funcţiei template, este considerat caz
exceptat.

14
 Exemple:
//comparare valori, supraincarcare template
#include <iostream>
using namespace std;
template <class X> void compara(X a, X b); // caz template general
void compara(int a, int b) ; // caz exceptat
template <> void compara (float a, float b); //specializare explicita
void compara(int a, int b, int c); //supraincarcare explicita trei
parametri
int main(void){
int x, y, a, b, c;
float m, n;
double d, e;
cout << "\nPrimul numar intreg: "; cin >> x;
cout << "\n al doilea numar intreg: "; cin >> y;
15
cout << "\nPrimul numar flotant: "; cin >> m;
cout << "\n al doilea numar flotant: "; cin >> n;
cout << "\n Alti trei intregi";
cout << "\nPrimul numar intreg: "; cin >> a;
cout << "\n al doilea numar intreg: "; cin >> b;
cout << "\n al treilea numar intreg: "; cin >> c;
cout << "\nPrimul numar double: "; cin >> d;
cout << "\n al doilea numar double: "; cin >> e;
compara(x, y); // supraincarcare intregi caz exceptat
cout<<endl;
compara(d, e); // apel template general
cout<<endl;
compara(a, b, c); // supraincarcare semnatura diferita
cout<<endl;
compara<>(m, n); // apel specializare explicita
cout<<endl;
return 0;
}
16
template <class X> void compara(X a, X b){
cout << "\n Compar date ne-intregi!-template general\n";
if(a>b)cout << "\nValoarea "<<a<<" este mai mare.";
else cout << "\nValoarea "<<b<<" este mai mare.";
}// caz template general

void compara(int a, int b){


cout << "\n Compar doi intregi!- caz exceptat\n";
if(a>b)
cout << "\nValoarea "<<a<<" este mai mare.";
else
cout << "\nValoarea "<<b<<" este mai mare.";
}// caz exceptat

template <> void compara (float a, float b){


cout<<"\nSpecializare explicita!-numere reale\n";
if(a>b) cout << "\nValoarea "<<a<<" este mai mare.";
else cout << "\nValoarea "<<b<<" este mai mare.";
17
}//specializare explicita
void compara(int a, int b, int c){
int sir[3];
int i, max;
cout << "\n Compar TREI intregi!\n";
sir[0] = a;
sir[1] = b;
sir[2] = c;
max = sir[0];
for(i=0; i<3; i++)
if(sir[i]>max)
max = sir[i];
// SAU max=((a) > (b)) ? (((a) > (c)) ? (a) : (c)) : (((b) > (c)) ? (b) : (c)) ;
cout << "\nValoarea "<<max<<" este mai mare.";
}//supraincarcare explicita trei parametri 18
PARAMETRII TEMPLATE AI FUNCŢIILOR TEMPLATE

 Funcţiile template pot avea diferite tipuri de parametri:


a) tip – parametri template introduşi cu class sau typename
b) non-tip parametri template, pot fi:
- int, enum
- pointer la un obiect sau funcţie
- referinţă la un obiect sau funcţie
- pointer la un membru al clasei
c) parametri template- template

 O funcţie template sau clasă template nu poate schimba


valoarea unui parametru non-tip.
 Argumentele de apel trebuie să fie expresii constante.

 Antetul template-ului poate conţine parametri tip sau non-tip.


19
 Exemple pentru a) şi b)
// parametri tip şi non-tip functii generice
#include <iostream>
using namespace std;
template <typename T, int n> void scrie (T t[]);

int main(){
int a[] ={1,2,3,4,5};
scrie<int,5> (a);
return 0;
}

template <typename T, int n> void scrie (T t[]){


for (int i=0; i<n; i++) cout<< t[i] << " ";
}
20
// parametru tip pentru template
#include <iostream>
using namespace std;

template<class T> T media(T tTab[], int nElem);


int main(){
int iTab[5] = {100, 200, 400, 500, 1000};
float fTab[3] = { 1.55f, 5.44f, 12.36f};
cout << " Media intregi= " <<media(iTab, 5)<<endl;
cout << " Media flotante = " << media(fTab, 3)<<endl;

return 0;
}

template<class T> T media(T tTab[], int nElem){


T tSuma = T(); // tSuma = 0
for (int i = 0; i < nElem; ++i){
tSuma += tTab[i];
} 21
return tSuma/nElem;
}
 c) parametri template-template
parametrul template-ului este de tip template, prin declararea unei clase
template în antetul funcţiei template
Acest template va fi introdus doar prin class or typename (structure,
union nu sunt permise).

Un antet simplu:
template <typename A>

 Dacă A va fi înlocuit cu clasă template :


template < template <typename T> class A>,
A este un template-template

 Exemple:
c1) template-template functii
template <template <typename T> class A > void F(A<char>a){...}
c2) template-template pentru clase:
template<template <class T> class A> class X { };
template<class T> class Y { }; 22

X<Y> a;
RESTRICŢII PENTRU FUNCŢIILE GENERICE
Limitări:
1. Funcţiile generice realizează aceleaşi operaţii indiferent de tipul
de date pe care le primesc la intrare. (dezavantaj ce poate fi
eliminat prin supraîncărcare explicită, când putem impune o altă
abordare pentru acea metodă sau prin specializare).
2. Funcţiile template nu admit parametri template impliciţi, dar
admit parametri de apel impliciţi.
3. O funcţie virtuală nu poate fi template
4. Destructorii nu pot fi template;
Obs! Dacă o funcţie template se potriveşte uneia dintre
specializări şi unei supraîncărcări, atunci are prioritate
supraîncărcarea non-template. Pentru apelul specializării explicite
se va introduce după numele metodei construcţia < >.
Funcţiile template au suferit diferite modificări de la o
versiune la alta: '98, '0x,' 1y ‘, 2z 23
EXEMPLE:
a) template <class T1=int, int n=7> void F(T1 t) {…} // Err. Nu pot fi specificati
parametri template impliciti

b) //parametri impliciti de apel pentru functia template


#include <iostream>
using namespace std;

template <typename T1, typename T2> void F(T1 t1=10, T2 t2='A');


int main(){
F<int, char>(); // valori implicite t1=10, t2=‘A’
F<int, char>(100); // t1=100, t2=‘A’
F<int, char> (100, 'W'); // t1=100, t2=‘W’
//F(); // Err nu se deduc T1 si T2
F<char, float>('A', 2.14f);

} //main

template <typename T1, typename T2> void F(T1 t1, T2 t2){


cout<< "\nT1= " << t1;
cout<< "\nT2= " << t2; 24

}
CLASE TEMPLATE (GENERICE)

 O altă metodă de generalizare a proceselor o constituie


implementarea claselor generice.
 La definirea claselor template sunt descrişi algoritmii
utilizaţi, tipurile datelor utilizate vor fi specificate la crearea
obiectelor din clasă.
 Clasele generice sunt utile când atributele şi algoritmii
acestora au un anumit grad de generalitate.
 Datele şi metodele din clasele generice pot utiliza
parametrii template ca tipuri generice.

25
SINTAXA DE DEFINIRE A UNEI CLASE GENERICE
template <class tipg1[,… class tipgi]> class Class_name{
………….. };
sau
template <typename tipg1[,…typename tipgi]> class Class_name{
…………..
};
unde tipgi este numele care substituie tipul de date utilizat de clasa

Exemple:
template <typename T> class B{
T a;
…………..
}; //B

template <typename T1, typename T2> class X{


T1 x;
T2 y; 26
…………..
}; //X
EXEMPLU:
#include <iostream>
using namespace std;

template <class STip> class Stiva{


private:
int Dim;
STip *Stack;
int Next;
public:
Stiva(int);
~Stiva(void);
int push(STip c);
int pop(STip &c);
int isEmpty(void);
int isFull(void); 27
};
template <class STip> Stiva <STip> :: Stiva(int dim_i){
Next = -1;
Dim = dim_i;
Stack = new STip [Dim];
}
template <class STip> Stiva <STip> :: ~Stiva(void){
delete [ ] Stack;
}
// test stiva goala
template <class STip> int Stiva <STip> :: isEmpty(void){
if (Next < 0) return 1;
else return 0;
}

28
// test stiva plina
template <class STip> int Stiva <STip> ::isFull(void){
if(Next >= Dim) return 1;
else return 0;
}

// introducere in stiva
template <class STip> int Stiva <STip> ::push(STip c){
if(isFull()) return 0;
Stack[++Next] = c;
return 1;
}
// extragere din stiva
template <class STip> int Stiva <STip> ::pop(STip &c){
if(isEmpty()) return 0;
c = Stack[Next--];
return 0; 29
}
int main(){
Stiva<int> Si(20);
Stiva<double> Sf(10);
Stiva<char> Sc(5);
Si.push(10);
Sf.push(10.10);
Sc.push('A');
int a=10;
double b=10.11;
char c;
Si.pop(a);
Sf.pop(b);
Sc.pop(c);
cout << a << " , "<< b <<" , "<< c;
return 0; 30
}//main
INSTANŢIEREA CLASELOR GENERICE
 Crearea unei noi definiţii a unei functii, clase sau membru al
unei clase utilizând declaraţia unui template se numeşte
instanţierea template-ului.
 Clasa obţinută în acest mod dintr-o clasă generică este o
clasă instanţiată. Instanţierile pot fi implicite sau explicite.

a) Instanţiere implicită
 Pentru instanţierea unui obiect poate fi utilizată instanţa
creată implicit de către compilator, caz în care template-ul
trebuie definit, nu doar declarat (metodele să fie definite).
 Pentru referinţe/ pointeri la obiecte, nu este necesar ca
clasa să fie definită, este suficient să fie declarată

31
template <class T> class A; // declararea clasei A

template <class T> class B {


T n;
};// definirea clasei B → putem defini obiecte folosind
constructorul implicit furnizat de compilator

int main(){
B <int> b; // declarare obiect- instantiere implicita
A <char> *pc; // declarare pointer, nu e nevoie definire clasa A
// A <float> obj; // Eroare, clasa e doar declarata si trebuie definita
….
}//main

A <T> template folosit pentru declararea sau definirea pointeri/obiecte.


A obj; // nu este permis pentru ca nu se specifica ce clasa trebuie
32
instantiata
b) Instanţierea explicită
Se poate specifica în mod explicit compilatorului când trebuie
să genereze o definiţie dintr-un template → instanţiere
explicită. In felul acesta se va inlocui instantierea implicită a
unei specializări a acelui template.
– 2 forme: declarare instanţiere explicită şi definire
instanţiere explicită
Declararea de instanţiere explicită permite instanţierea unei clase generice
fără a o utiliza în cod - util la crearea de biblioteci (fisiere .lib) ce folosesc
template-uri pentru distribuire; definițiile genericelor neinstanţiate nu sunt
puse în fişierele .obj.
Sintaxa declarare instantiere explicită:
extern template class template-name < argument-list > ;
(începând cu C++1y)
Obs: aici extern este folosit pentru a specifica o declaratie de instantiere
explicita 33
//SampleA.h:
template<typename T, T val> class A {
public:
T getVal();
};
extern template class A<int, 55>; // declaratie instantiere explicita
template<class T, T val> T A<T, val>::getVal(void) {
return val;
}
//SampleB.cpp:
#include "SampleA.h"
template class A<int, 55>; // definire instantiere explicita
//Source.cpp
#include<iostream>
using namespace std;
#include "SampleA.h"
int main(void) { 34

cout << A<int, 55>().getVal(); }


Definire instantiere explicita
O definiție explicită de instanțiere este o instanțiere a unei
specializări a template-ului sau a membrilor săi
 Clasa generică:
template <class T> class X {
…;
}; // definirea clasei X
X <float> *p; // declarare pointer

template class X <float>; // definire instantiere explicita a


compilatorului pentru class X <float> , chiar daca contextul nu
necesita asta pana cand instantiem obiecte din acea clasa

Dacă avem o declaraţie de instanţiere explicită a unei metode


membre sau a unei clase, dar nu există definirea instanţierii 35
explicite in program, compilatorul va semnala eroare.
Definiţia creată prin instanţierea template-ului pentru a
utiliza un set specific de argumente template se numeşte
specializare.
template <class T> class X {
…;
}; // definirea clasei X

template <> class X<float> {



}; // varianta specializată

Instanțierea explicită nu are efect dacă inainte de aceasta


apare o specializare explicită, pentru același set de
argumente ale template-ului 36
MEMBRII CLASEI GENERICE
 Clasele care nu sunt generice pot conţine metode membre
generice declarate separat în clase. Uzual în C ++,
funcţiile template sunt definite în afara claselor şi metodele
template se definesc în clase.

 Clasele template pot avea membri template sau non-


template şi dacă au metode, ele sunt implicit template.

 Metodele claselor template pot fi funcţii template cu


aceiaşi parametri ca şi clasa sau pot avea parametri
template proprii.

37
A) METODE TEMPLATE IN CLASE NON-TEMPLATE
#include <iostream>
using namespace std;
class A{
public: template <typename T1, typename T2> void F(T1 t1, T2 t2){
cout<< "\nT1= " << t1;
cout<< "\nT2= " << t2;
}
}; //A
int main(){
A a;
a.F<int, char>(10,'A'); // specificare explicita
a.F(100, 'W'); // deducere
cin.get();
}
Dacă o metodă template este doar declarată în clasă, ea va fi definită în38
afara clasei folosind antetul template-ului:
// METODE TEMPLATE IN CLASE NON-TEMPLATE DEFINITE IN AFARA CLASEI

#include <iostream>
using namespace std;
class A{
public: template <typename T1, typename T2> void F(T1 t1, T2 t2);
}; //A
int main(){
A a;
a.F<int, char>(10,'A'); // specificare explicita
a.F(100, 'W'); // deducere
cin.get(); return 0;
}
template <typename T1, typename T2> void A:: F(T1 t1, T2 t2){
cout<< "\nT1= " << t1;
cout<< "\nT2= " << t2; 39
}
B) METODE TEMPLATE IN CLASE TEMPLATE
//membrii template in clase template

#include <iostream>
#include <string>
using namespace std;
template <class T, int DIM> class Stiva {
T st[DIM];
int next;
public:
Stiva(): next(0) { };
void push (T val) { st[next++] = val;}
T pop () {return st[--next];}
void display() const {
for (int i=next-1; i>=0;i--)
cout <<" "<< st[i] << " ;";
cout <<'\n';
}
40
};
int main(){
Stiva <string, 100> s1; // Stiva de string-uri
s1.push(" Unu ");
s1.push(" Doi ");
s1.push(" Trei ");
s1.display();
cout << s1.pop();
cout << s1.pop();
s1.display();

Stiva <int, 1000> s2; // Stiva de intregi


s2.push(10);
s2.push(20);
s2.push(30);
s2.display();
cout << s2.pop()<<" ";
cout << s2.pop()<<" ";
s2.display();
cin.get(); 41
}
 Metodele definite în clase nu specifică template în antet,
ele sunt considerate template în mod implicit
 Clasele template acceptă parametri template tip, non-tip și
template-template
 Specificatorul const ca postfix la metoda display() indică
faptul ca metoda nu modifică nici una din datele membre
ale clasei, adica *this nu se modifică
 Numele claselor instanţiate include lista de argumente între
<>
 O metodă definită în afara clasei va indica apartenenţa prin
operatorul de scop :: şi va conţine şi cuvântul template în
antet.

42
PARAMETRII TEMPLATE AI CLASELOR
Clasele template suportă aceleaşi tipuri de parametri ca şi
metodele template: tip, non-tip, template-template
Exemplu :
template <class T, int n, template <class N> class A> class B{
//……
}; //B tip, non-tip, template-template
template <class T > class X {
//………..
}; //X
int main (){
B <char, 10, X> b;
}
Parametrul N din definirea clasei B putea fi omis:
template <class T, int n, template <class > class A> class B{
//……
}; //B 43
PARAMETRI TEMPLATE IMPLICIŢI
 Template-urile de clasă acceptă parametri template impliciţi, spre
deosebire de metodele generice
a) Parametri tip

// clase cu parametri tip impliciti


class A {
}; //A

template <typename T=A> class B{


T t;
}; //B

int main (){


B <int>b1; //tip int
B <> b2; // tip implicit A
//B b; 44

}
b) Parametrii non-tip
- Au aceleaşi restricții ca la metodele template, adică pot fi
int, enum, pointeri la obiecte, atribute sau metode, sau pot
fi referinţe.
- Nu se admit tipuri reale (float, double), doar pointeri la
aceste tipuri
- Valorile implicite trebuie să fie expresii constante

c) Parametrii template-template
Apar atunci când o clasă template va admite ca şi argument
o altă clasă template. Astfel, o clasă A cu un parametru
template X poate fi instanţiată cu diferite argumente de tip
template de clasă.
45
Exemple :
//arg. implicite template-template
class D {
}; //D non template

template <typename T= D> class B{…


}; //B template, implicit tip D

template <typename T = int> class C {…


}; //C template, implicit int

template <template <typename> class X = B > class A{


}; //A clasa, template-template, implicit B pentru X

int main ( ){
A <C> a1; // X==C, C<T> == C<int>
A < > a2; // X==B, B<T> == B<D>
//A<D> a3; // Err D e clasa non template 46

}
SPECIALIZĂRILE CLASELOR
Şi clasele template admit specializări la fel ca şi metodele, iar în
plus permit şi specializări parţiale.
Specializarea se face când se doreşte o definiţie diferită de cea
generală, pentru anumite tipuri.
Numele specializării este format din numele clasei la care se
adaugă lista de argumente template cuprinsă între paranteze
unghiulare, < >.

a) Specializarea completă (explicită)


Specializarea e particularizarea parametrilor template-ului
primar. Ea se face prin precedarea numelui clasei de construcţia:
template < > şi specificarea argumentelor template pentru toţi
parametrii:

47
template <typename T> class A {
//…..
}; //A template primar
Nume
specializare
template <> class A <int>{
//….
}; // Specializare explicita A <int> pt. T = int

template <> class A <char>{


//….
}; // Specializare explicita A <char> pt. T = char

48
Tipurile pentru care se face specializarea se specifică ca şi argumente
template imediat după numele clasei, între < >.
Corpul specializării ar trebui să difere de corpul clasei primare.

//specializare explicita clase template


#include <iostream>
using namespace std;
template <typename T> class Str{
T t[100];
int n;
public:
void read (){
cout <<"\n Nr elem, apoi elementele sirului: ";
cin >>n;
for (int i=0; i<n; i++)
cin >> t[i]; } //read
void display() const {
for (int i=0;i<n;i++)
49
cout << "\nElementele sir: "<< t[i] << " "; } //display
}; //Str primar
template < > class Str <char> {
char t[100];
public:
void read() const {
cout <<"\n Introduceti sir caractere: ";
cin >>(char*) t;
} //read
void display() const {
cout << " \n Sirul: " << t;
} //display
};//Specializare

50
Specializarea explicită trebuie să furnizeze argumente
pentru toţi parametrii template.
int main(){
Str <int> s1;
cout <<"\n Tip generic, int \n";
s1.read();
s1.display();
Str <char> s2; //specializare
s2.read();
s2.display();
cin.ignore();
cin.get();
return 0;
51
}//main
b) Specializarea parţială
Dacă specializăm o clasă template păstrând cel puţin un parametru
generic, atunci avem o specializare parţială. Specializările parţiale reduc
generalitatea template-ului primar. Ele nu sunt condiţionate de existenţa
definiţiei complete a template-ului primar.

//specializare partiala clase template


template <typename T1, typename T2> class A{
//....
};//template primar

template <typename T> class A <T, char>{


//....
};//Specializare T2 la char

template <typename T> class A <float, T>{


52
//....
};//Specializare T1 la float
int main(){
A <char, char> a1; // Spec. A <T, char>
A <char, int> a2; //Sablon primar A <T1, T2>
A <float, int> a3; //Spec. A <float, T>
return 0;
}//main

53
MOŞTENIREA ŞI COMPOZIŢIA CLASELOR TEMPLATE
C++ e considerat un limbaj multi-paradigmă. Suportă :
- programarea structurată, procedurală, din limbajul C
- P.O.O
- programarea generică
- programarea funcţională odată cu funcţiile lambda
introduse în C++1y

 Exemplele următoare specifică elementele importante


legate de moştenire şi compoziţie

54
A) CLASA TEMPLATE MOȘTENEȘTE CLASA NON-TEMPLATE
//mostenire clasa non-template
class A {
//....
};//A

class B: public A
{
//...
};//B non-template

template <typename T> class C : public A


{
T d;
};//C

int main (){


B ob1; 55
C <int> ob2;
}//main
B) CLASA TEMPLATE MOŞTENEŞTE CLASA TEMPLATE

//mostenire template clasa template


template <typename T> class A {
//....
}; //A template

template <typename T1, typename T2> class C : public A <T2>


{
T1 d;
}; //C

int main (){


C <int, char> ob2; //T1 int, T2 char
return 0;
56
} //main
C) NUMELE CLASEI MOŞTENITE ESTE UNUL DIN PARAMETRII TEMPLATE AI
CLASEI DERIVATE
//mostenire - clasa de baza e parametru template la clasa derivata
template <typename T> class B {
//....
}; //B template baza

template <typename T1, typename T2> class D : public T2


{
T1 d;
}; //Derivat

int main (){


D <int, B<char>> ob1; //T1 int, T2 B<char>
// D<int, char> ob2; // char nu e tip class 57
} //main
Compoziţia (Conţinerea)

 Când un obiect conţine cel puţin un membru care la rândul lui e obiect
al cărui tip diferă de cel al clasei
//continere
template <typename T> class A {
T x; // obiect continut
//....
};//A template baza
template <typename T1, typename T2> class B
{
A<T1> a; // a obiect continut -- compozitie
T2 b; // b obiect continut
};//Derivat

int main (){


B <int, char> ob1; //T1 int, T2 char, si presupune instantierea prealabila
a lui A<int>
B <int, A<char>> ob2; // T1 e int, T2 e A<char> 58

}//main
RESTRICŢII IMPUSE CLASELOR TEMPLATE
Sunt necesare următoarele restricţii în cazul implementării şi
folosirii claselor template:
 la fiecare instanţiere a tipului template suntem obligaţi să
înlocuim tipul generic cu un tip concret;
 specificarea tipului se face sub formă de parametru al clasei
template;
 la fiecare instanţiere, tipul concret va lua locul tipului
generic;
Tipurile generice folosite în declaraţiile claselor template pot fi
înlocuite cu:
 tipuri de date predefinite (int, float, etc.)

 tipuri de date utilizator definite de programator;

 pointeri la funcţii;
59
 expresii constante.
#include <iostream>
using namespace std;
template <class X, int n> class myTemplate{
X tab[n];
int i;
public:
void init(){ for(i=0; i<n; i++){
cout << "\nElementul "<<i+1<<": ";
cin >> tab[i]; }
}//init

void arata(){ for(i=0; i<n; i++)


cout << "\nElementul "<<i+1<<": "<<tab[i];
} //arata
};//myTemplate
60
int main(void){
myTemplate <int, 3> ob1;
ob1.init();
ob1.arata();
return 0;
} //main

61
Intrebari:

 Cum se declara o functie/metoda template?


 Cum se apeleaza o functie template? Trebuie
specificate tipurile parametrilor sau pot fi deduse de
catre compilator? Dar daca functia are parametri de
apel expliciti?
 Ce intelegeti prin supraincarcarea functiilor
template?
 Ce intelegeti prin caz exceptat, in cazul
supraincarcarii functiilor template?
 Ce intelegeti prin specializare explicita? Care este
sintaxa antetului functiei?
 Care sunt tipurile parametrilor template-ului? 62
 Funcţiile template admit parametri template
impliciţi? Dar parametri de apel impliciţi ?
 Care este sintaxa pentru declaratia unei clase
template?
 Cum se realizeaza instantierea unui obiect dintr-o
clasa template?
 Ce parametri template admite o clasa template?

 Clasele template acceptă parametri template


impliciţi?
 Cum se realizeaza specializarea explicita a unei clase
template?

63
PROGRAMAREA CALCULATOARELOR –
ALGORITMI
MODUL 3

CURS 12
BIBLIOTECA STL
CUPRINS
 Introducere STL
 Containere
 Iteratori
 Algoritmi
 Obiecte funcţii

2
INTRODUCERE STL (STANDARD TEMPLATE LIBRARY)
 STL este o bibliotecă de componente, cum ar fi
containerele şi algoritmii, ce asigură interoperabilitatea
între componentele predefinite (incluse în limbaj) şi cele
definite de utilizator
 Astfel un algoritm STL poate opera şi pe containere
definite de utilizator, iar algoritmi definiţi de utlizator pot
opera şi pe containere STL, dacă respectă anumite
cerinţe

 A fost concepută de către Alex Stepanov şi Meng Lee în


cadrul laboratoarelor HP de la Palo Alto

 Biblioteca STL a fost inclusă în standardul ANSI C++ în


1994 (1997).
 Versiunile C++ 1y/2z aduc îmbunătăţiri pentru 3
containerele de bază şi facilităţi noi
STRUCTURA STL
 Să considerăm următoarea situaţie în care componentele
software sunt imaginate ca un spaţiu cu mai multe
dimensiuni:
int, double, char, ...

k j

cautari, sortari, ... tablou, lista,…

4
STRUCTURA STL (CONT.)
 In acest caz trebuie proiectate i*j*k versiuni de cod pentru
a acoperi toate situaţiile posibile
 Dacă am utiliza funcţiile template (generice), axa “i”
poate lipsi şi ramân necesare j*k versiuni de cod:
 de exemplu, vom avea o singură implementare a listei
înlănţuite pentru toate tipurile de date.
 Următorul pas este de a face algoritmii să lucreze pentru
tipuri diferite de containere (tablouri, liste,…).
 In acest mod vor mai fi necesare doar j+k versiuni de
cod.

5
COMPONENTE STL
 Principalele componente ale STL sunt:
 containere:
 obiecte ce pot păstra şi administra obiecte; conţin structurile de
date suportate de STL;
 sunt definite clase generice ce conţin aceste structuri şi metode
pentru manipularea datelor;
 algoritmi:
 proceduri ce pot opera pe diferite containere;
 iteratori:
 abstractizări ale accesului algoritmilor la containere, astfel încât
algoritmii să poată opera pe diverse containere
 obiecte funcţii (functor):
 clase ce au definit operatorul apel de funcţie ( );
 adaptor:
 încapsulează o componentă pentru a furniza o altă interfaţă (de 6
exemplu pentru a obţine o stivă dintr-o listă)
DIAGRAMA STL

7
AVANTAJE STL
 eficienţa: nu folosesc derivarea claselor şi nici funcţii
virtuale şi ca urmare, STL este foarte apropiată de
codificarea directă
 siguranţa tipurilor (type safety): prin utilizarea
extensivă a şabloanelor
 flexibilitatea: suport mai bun pentru programarea
generică; algoritmii generici pot fi aplicaţi pe diferite
structuri, inclusiv cele predefinite în C++ (şiruri,
tablouri)
 structura simplă: biblioteca este destul de mică
 Compilarea separată a template-urilor
 Mecanism generalizat de iniţializare a listelor, etc.
8
UTILIZARE STL
 tot ce ţine de STL este plasat în spaţiul de nume
standard:
 acesta trebuie specificat printr-o directivă using :

using namespace std ;


 există şi posibilitatea utilizării numai a unei secţiuni din
acest spatiu de nume astfel :
using namespace std ::string ;
Fisiere header in biblioteca STL: algorithm, deque,
functional, iterator, vector, list, map, memory, numeric,
queue, set, stack, utility.
Cum le utilizăm? Se include un fișier antet specific:
#include <vector>
9
using namespace std;
CONTAINERE STL
 Sunt tipuri de date abstracte (clase) ale căror obiecte pot
fi folosite pentru a construi colecţii de date de acelaşi tip
 Caracteristici :
 toate containerele sunt parametrizate prin tipul pe care îl
conţin
 fiecare container declară un iterator şi metode speciale
pentru iteratori
 Categorii :
 containere secvenţă
 containere asociative
 adaptori
 speciale

10
 containere secvență: datele sunt ordonate liniar şi permit
căutări pe baza cheii (array, vector, forward_list, list, deque).
Sunt colecții ordonate în care fiecare element are o anumită
poziție. Termenul “ordonat” nu are semnificația de crescător/
descrescător, ci se referă la o anumită poziție. Poziția depinde
de momentul și locul inserării, dar este independentă de
valoarea elementului.

 containere asociative : datele sunt păstrate în structuri de date


adecvate pentru căutări asociative (implementeaza tablouri
asociative= colectie de perechi): set, map, multiset, multimap.
Sunt colecții ordonate, în care poziția actuală a unui element
depinde de valoarea sa, în funcție de un criteriu de sortare.
C++1y a introdus Unordered Associative Containers
(unordered_map, unordered_set - containere asociative de tip
hash ce pot fi şi multimap şi multiset), care sunt colecții
neordonate. 11
 adaptori : furnizează interfeţe diferite, dar specifice pentru
containerele de mai sus. Sunt construite pe alte containere și
sunt utilizate pentru a utiliza reguli de acces care nu se
bazează pe iteratori. Stack și queue se obțin din deque.

 speciale: sunt pseudo-containere, având unele limitări


(string, bitset(tablouri de tip bool)), şi de obicei nu sunt
considerate ca o categorie aparte.

12
Au mai apărut:
 forward_list - container secvenţial pentru liste simplu
înlănţuite
 unordered_map, unordered_set - containere asociative de
tip hash ce pot fi şi multimap şi multiset
 array - container de tip tablou unidimensional de
dimensiune fixă
 bitset - container tablou de tip bool, etc.

 C++98 a introdus un container special numit valarray


pentru operații matematice eficiente pe tablouri

13
Categorie Container Caracteristici

Păstreaza datele în mod liniar şi în zona contiguă de memorie


vector Similar cu tablourile
Permit inserări rapide dar numai la sfârşit

Secventiale
list Lista dublu înlănţuită ce permite inserări rapide în orice poziţie

Organizare liniară dar stocare neliniară.


deque Permite inserări rapide la extremităţi

multiset Implementare pentru tipul set dar sunt permise duplicări


Permite căutari asociative rapide

set Implementare pentru tipul set (elementul este caracterizat de valoarea sa –


tipic implementat ca arbori binari de cautare), dar fără duplicări
Asociative Permite cautări asociative rapide
multimap Permite implementarea unei structuri în care se foloseşte o mapare cheie->
mai multe valori (1 to many)

map Permite implementarea unei structuri în care se foloseste o mapare cheie->o


valoare (1 to 1)

stack Implementarea unei structuri Last In First Out

queue Implementarea unei structuri First In First Out


Adaptoare

priority_queue O coadă ce păstrează elementele sortate


14
CONTAINERE STL:HTTPS://WWW.CPLUSPLUS.COM/REFERENCE/STL/

15
STL ORDERED AND SEQUENCE CONTAINERS -
HTTPS://WWW.GEEKSFORGEEKS.ORG/THE-C-STANDARD-TEMPLATE-LIBRARY-
STL/

16
CONTAINERE STL ADAPTIVE SI NEORDONATE:

17
CONTAINERE (CONT.)
 Declararea unui container se face astfel:
Container <Tip_concret> c;

 Tipul concret trebuie să asigure:


 constructor şi destructor
 constructor de copiere
 supraîncărcarea operatorului de atribuire

 Exemplu :
vector < int> tab1(10) ;
vector <float> tab2(20) ;

18
Container de tip vector
 gestionează elementele sale într-un tablou dinamic
 acces aleatoriu.
 Structura unui vector arată astfel :

 Se descrie printr-o clasă template cu parametri impliciţi, astfel :


template < typename T, typename Alloc = allocator<T> > class vector;
-T e tipul elementelor din container
- Alloc, allocator de clasă ce admite ca argument template implicit,
allocator<T> (pentru vector se utilizează cel oferit implicit de STL)

 vector <int> v ;
 vector <bool>; este o specializare a containerului vector
introdusă în noile versiuni ale C++. 19
CONTAINER: VECTOR
 #include <vector>
 Funcţii:
 begin() returnează un iterator către primul element
 end() returnează un iterator după ultimul element
 push_back(...) adaugă un element la sfârşitul vectorului
 pop_back(...) extrage un element de la sfârşit
 swap(…) interschimbă elementele a doi vectori,
 insert( , ) inserează un element
 erase( , ) şterge un element (sau mai multe)
 size() returnează numărul de elemente din vector
 capacity() dă capacitatea (în număr de elemente) înainte
de a face o nouă realocare
 reserve() alocă spaţiu pentru un număr de elemente
 resize( , ) redimensionează un vector
 empty() returnează True dacă vectorul este gol
20
 [] operator de acces
EXEMPLU: VECTOR
/* operatii de adaugare elemente in vector, interschimbare si asignare
vectori */
#include < iostream >
#include < vector >
using namespace std;
void print (vector < double >&vector_);

int main (void) {


vector < double > v1;
v1.push_back (32.1);
v1.push_back (40.5);
cout << "v1 = ";
print (v1); 21
vector< double > v2;
v2.push_back (3.56);
cout << "v2 = ";
print (v2);
v1.swap (v2);
cout << "v1 = ";
print (v1);
cout << "v2 = ";
print (v2);
v2 = v1;
cout << "v2 = ";
print (v2);
return 0;}
void print (vector < double >&vector_) {
for (int i = 0; i < vector_.size (); i++)
cout << vector_[i] << " ";
cout << endl; 22

}
// vector de valori intregi
#include <iostream>
#include <vector>
//using namespace std;

int main( ) {
std::vector<int> v = { 0, 1, 2, 3, 4, 5 };
for (const int& i : v) // acces prin referinta la valori constante

23
std::cout << i << ' ';
std::cout << '\n';
for (auto i : v) // acces prin valoare
std::cout << i << ' ';
std::cout << '\n';
for (auto&& i : v) // acces prin rvalue reference - tipul lui i este int&
std::cout << i << ' ';
std::cout << '\n';

const auto& cv = v;
for (auto&& i : cv) / /acces prin rvalue reference, tipul lui i este const int&
std::cout << i << ' ';
std::cout << '\n’;
} //
//vector, swap( )
#include <vector>
#include <iostream>
using namespace std;

int main( ){
vector <int> vec1, vec2;
vec1.push_back(4);
vec1.push_back(7);
vec1.push_back(2);
vec1.push_back(12);
cout << "vec1 data: ";
for (auto &i: vec1) cout << i << ' ‘;
cout << endl;

vec2.push_back(11);
vec2.push_back(21);
vec2.push_back(30);
cout << "vec2 data: ";
for (auto &i : vec2) cout << i << ' ';
cout << endl; 24
cout << "The number of elements in vec1 = " << vec1.size() << endl;
cout << "The number of elements in vec2 = " << vec2.size() << endl;
cout << endl;

cout << "Operation: vec1.swap(vec2)\n" << endl;


vec1.swap(vec2);
cout << "The number of elements in v1 = " << vec1.size() << endl;
cout << "The number of elements in v2 = " << vec2.size() << endl;
cout << "vec1 data: ";
for (auto &i :vec1) cout << i << ' ';
cout << endl;
cout << "vec2 data: ";
for (auto &i :vec2) cout << i << ' ';
cout << endl;
return 0;
} 25
CONTAINER: LIST
 #include <list>
 Funcţii:
 begin()
 end()
 push_front(...) adaugă un element în faţa listei
 pop_front(...) extrage un element din faţa listei
 push_back(...)
 pop_back(...)
 swap( , )
 erase(...)
 insert( , )
 size()
 capacity()
 empty()
 splice( , ) mută conţinutul unei liste, dintr-o anumită
poziţie, într-o altă listă
 merge( ) interclasează două liste sortate 26
 sort() sortează o listă
EXEMPLU: LIST
#include < iostream >
#include < list >
using namespace std;
int array1 [ ] = { 9, 16, 36 };
int array2 [ ] = { 1, 4 };
int main (void) {
list< int > l1 (array1, array1 + 3);
list< int > l2 (array2, array2 + 2);
list< int >::iterator i1 = l1.begin ();
l1.splice (i1, l2); // transfera elem din l2 in l1, incepand cu poz. i1
list< int >::iterator i2 = l1.begin ();
cout<<"Lista1: ";
while (i2 != l1.end ())
cout << *i2++ << " ";
cout<<endl;
27
return 0;
}
CONTAINER DE TIP DEQUE
 deque - coada cu două capete. Este un tablou dinamic,
care este implementat astfel încât acesta să poată
crește în ambele direcții.
 introducerea rapidă a elementelor la sfârșit și la început.

 inserarea elementelor in mijloc are nevoie de timp


pentru că elementele trebuie mutate. Structura deque
poate fi descrisă după cum urmează:

28
CONTAINER: DEQUE
 #include <deque>

 Funcţii
 begin() iterator la inceput
 end() iterator la sfarsit
 push_front(...) adauga element la inceput
 pop_front(...) extrage primul element
 push_back(...) adauga element la sfarsit
 pop_back(...) extrage ultimul element
 swap( , ) interschimba continutul
 insert( , )
 size()
 capacity()
 empty() 29
 []
EXEMPLU: DEQUE
/*Adauga elemente la inceputul si sfarsitul cozii, sterge de la
inceput, modifica valori, afiseaza */
#include < iostream >
#include < deque >
using namespace std;
int main ()
{
deque< int > d;
d.push_back (4);
d.push_back (9);
d.push_back (16);
d.push_front (1);
30
for (int i = 0; i < d.size (); i++)
cout << "d[" << i << "] = " << d[i] << endl;
cout << endl;

d.pop_front ();
d[2] = 25;
for (int i = 0; i < d.size (); i++)
cout << "d[" << i << "] = " << d[i] << endl;

return 0;
}

31
stack și queue folosesc containerul deque <T> ca adaptor
implicit.
template <typename T, typename Container = deque<T> >
class stack;
template <typename T, typename Container = deque<T> >
class queue;

priority_queue (coada ordonată) este definită astfel:


template <typename T, typename Container = vector<T>,
typename Compare = less<typename Container::value_type>
> class priority_queue;

32
La containerele asociative elementele în containere sunt
referite de cheie și nu de poziția lor absolută în container. Aici
avem:
Set - containere care stochează elemente unice după o
anumită ordine. Fiecare element este identificat prin valoare,
fiecare valoare este unica (reprezinta chiar cheia). Nu se poate
modifica valoarea elementului, dar poate fi inserat sau sters din
container.
template < typename T, // set::key_type/value_type
typename Compare = less<T>, // set::key_compare/value_compare
typename Alloc = allocator<T> > // set::allocator_type
class set;
Compare - criteriul de ordonare din set cu argumentul implicit,
less<T>. (ordonare crescatoare)
Tipic sunt implementate ca arbori de cautare binara.
33

Multiset, permite chei duplicate.


Map - sunt containere asociative care stochează elemente
formate printr-o combinație valoare cheie → valoare mapată,
în urma unei ordini specifice:

template < typename Key, // map::key_type


typename T, // map::mapped_type
typename Compare = less<Key>, // map::key_compare
typename Alloc = allocator<pair<const Key,T> > //map::allocator_type
class map;

In map valorile cheie sunt folosite in general pentru a sorta și


identifica în mod unic elementele, în timp ce valorile mapate
păstrează conținutul asociat acelor chei.

Multimap - este definit în același mod și permite duplicate


pentru chei. 34
ITERATORI
 Iteratorii acţionează ca intermediari între algoritmi şi
containere

 Asigură accesul la obiectele stocate într-un container

 Sunt generalizări ale pointerilor şi permit tratarea unitară


a tipurilor diferite de date

 Pot fi folosiţi pentru a traversa colecţii de date păstrate în


containere

35
ITERATORI (CONT.)
 Iteratorii unei colecţii particulare (container) sunt definiţi
în clasa asociată colecţiei, sub forma unor construcţii
typedef :

 std::vector<std::string>::iterator
 std::vector<std::string>::const_iterator (iterator ce indica
o valoare constantă)

36
ITERATORI (CONT.)
 Fiecare iterator trebuie să asigure :
 accesul la elementul indicat (pointat) de iterator :
 *iterator,
 iterator -> , pt. colectii de tip map

 poziţionarea pe elementul următor din container :


 ++iterator
 compararea a doi iteratori:
 iterator1 == iterator2
 iterator1 != iterator2
 Compilatorul nu verifică domeniile iteratorilor, adică
dacă doi iteratori indică pe acelaşi container sau nu
37
ITERATORI (CONT.)
 Containerele au metode ce returnează iteratori:
 begin ():
 returnează un iterator către primul element
 end ():
 returnează un iterator după ultimul element;
acest iterator poate fi folosit ca o santinelă
(marcaj) în cazul parcurgerii colecţiei
 se mai numeşte şi past the end

38
ITERATORI - UTILIZARE
Utilizarea unui iterator se face astfel:
Nume_Container ::iterator first, last; //declarare
first = Nume_container.begin(); //asignare
last = Nume_container.end();

sau:
//initializare
Nume_Container ::iterator first= Nume_container.begin();
Nume_Container ::iterator last= Nume_container.end();

39
CATEGORII DE ITERATORI
input Citeşte un element la un moment dat, în direcţia înainte
(forward)
output Scrie un element la un moment dat, în direcţia înainte
forward Citeste sau scrie un element la un moment dat, în
direcţia înainte
bidirectional Citire sau scriere, înainte sau înapoi
random access La fel ca cei bidirecţionali, plus salturi la orice distanţă
în cadrul colecţiei

Iteratori
de intrare
Iteratori cu Iteratori Iteratori
acces aleator bidirectionali forward
Iteratori
de iesire
40
OPERATORI PENTRU ITERATORI

Operatori comuni tuturor tipurilor de iteratori:


++i Avansează o poziţie şi returnează noua valoare i
i++ Avansează o poziţie şi returnează vechea valoare i
Iteratori de intrare:
*i Returnează o referinţă read-only la elementul din poziţia
dată de i
i==j Returnează TRUE dacă cei doi iteratori sunt pozitionaţi
pe acelaşi element (sau după ultimul element din
colecţie)
i!=j ReturneazăTRUE dacă i şi j sunt pozitionaţi pe elemente
diferite
Iteratori de ieşire:
*i Returnează o referinţă la elementul din poziţia dată de i
i=j Stabileşte pentru i aceeaşi poziţie ca şi pentru j 41
OPERATORI PENTRU ITERATORI (CONT.)
Iteratori bidirecţionali:
--i Retragere o poziţie şi returnează noua valoare pentru i
i-- Retragere o poziţie şi returnează vechea valoare pentru i

Iteratori cu acces aleator:


i +=n Avansează n poziţii, returnează noua valoare
pentru i
i-=n Retragere n poziţii, returnează noua valoare
pentru i
i+n Returnează un iterator poziţionat peste n
elemente după i
i-n Returnează un iterator poziţionat peste n
elemente în faţa lui i
i[n] Returnează o referinţă la elementul n din colecţie 42
Corespondenţa între containere și tipuri de iteratori :
vector random access
deque random access
list bidirectional
multiset bidirectional
set bidirectional
multimap bidirectional
map bidirectional
stack none
queue none
priority_queue none

43
EXEMPLE -- VECTOR
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> coll; // declarare container
// adauga elemente in container
for (int i=1; i<=6; ++i) {
coll.push_back(i);
}
// afisare elemente
for (int i=0; i<coll.size(); ++i) {
cout << coll[i] << ' ‘; // acces prin indexare
}
cout << endl;
44
return 0;
}
// SET
#include <iostream>
#include <set>
using namespace std;

int main(){
typedef std::set<int> IntSet; // tip colectie
IntSet coll; // container de tip set
// inserare elemente - elem vor fi ordonate
coll.insert(3);
coll.insert(1);
coll.insert(5);
coll.insert(4);
coll.insert(1);
coll.insert(6);
coll.insert(2); 45
// afisare elemente
IntSet::const_iterator pos;
for (pos = coll.begin(); pos != coll.end(); ++pos)
{
std::cout << *pos << ' ‘; // acces prin iterator
}
std::cout << std::endl;

return 0;
}

46
// LIST – cu extragere elem.
#include <iostream>
#include <list>
using namespace std;

int main(){
list<char> coll; // container lista
// adauga elemente
for (char c='a'; c<='z'; ++c) {
coll.push_back(c);
}
// se afiseaza primul element dupa care acesta este extras
while (! coll.empty()) {
cout << coll.front() << ' ‘; // afisare element din fata
coll.pop_front(); // extragere element din fata
}
cout << endl;
return 0;
} 47
// LIST – cu iterator
#include <iostream>
#include <list>
using namespace std;

int main(){
list<char> coll; // container lista
// adauga elemente
for (char c='a'; c<='z'; ++c) {
coll.push_back(c);
}
// afisare elemente
list<char>::const_iterator pos;
for (pos = coll.begin(); pos != coll.end(); ++pos) {
cout << *pos << ' '; // acces prin iterator
}
cout << endl;
return 0;
48
}
// MAP
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main(){
// container de tip map: cheia de tip string, valoarea de tip float
typedef map<string,float> StringFloatMap;

StringFloatMap coll;

// adauga elemente
coll["TVA"] = 0.19;
coll["Pi"] = 3.1415;
coll["Numar oarecare"] = 4983.223;
coll["Null"] = 0; 49
// afisare elemente
StringFloatMap::iterator pos;
for (pos = coll.begin(); pos != coll.end(); ++pos)
{
cout << "cheie: \"" << pos->first << "\" "
<< "valoare: " << pos->second << endl;
}

return 0;
}

OBS! Elementele sunt ordonate dupa cheie (lexicografic in acest50


caz)
// DEQUE

#include <iostream>
#include <deque>
using namespace std;

int main(){
deque<float> coll; // container de tip deque

// inserare elemente
for (int i=1; i<=6; ++i) {
coll.push_front(i*1.1);
}
// afisare elemente
for (int i=0; i<coll.size(); ++i) {
cout << coll[i] << ' ‘; // acces prin indexare
}
cout << endl; 51
return 0;
}
ALGORITMI
 Algoritmii STL sunt funcţii generice ce operează pe
containere
 Aceste funcţii nu au containerele ca argumente, ci doar
iteratori ce specifică parte sau containerele în intregime
 Astfel, algoritmii pot opera inclusiv pe tipuri de date ce nu
sunt containere
 Se realizează astfel o decuplare între algoritmi şi
containere prin intermediul iteratorilor
 Este foarte important ca acele containere să suporte
iteratorii necesari unui algoritm (vezi documentaţie)
 Algoritmii se afla in biblioteca <algorithm>, initial au fost
integrati peste 60 de algoritmi, acum fiind aproximativ 80
de algoritmi 52
ALGORITMI (CONT.)

 Exemplu :
template <class ForwardIterator>ForwardIterator min_element
(ForwardIterator first, ForwardIterator last);
 Acest algoritm necesită un container ce suportă cel puţin
un iterator de tip « forward »
 Tentativa de a utiliza un algoritm pe un container ce nu
asigură iteratorii necesari conduce la erori, uneori ciudate

53
CATEGORII DE ALGORITMI

 Operaţii pe secvenţe, ce nu produc modificări


 Aplică o funcţie tuturor elementelor (for_each…)
 Caută un element ce satisface o condiţie (find…)
 Numără elementele ce satisfac o condiţie (count…)
 Caută prima nepotrivire între două secvenţe
(mismatch…)
 Verifică dacă două secvenţe sunt egale (equal…)
 Caută prima potrivire a unei subsecvenţe în cadrul
unei alte secvenţe (search…)

54
 Operaţii pe secvenţe, ce produc modificări
 Copiază o secvenţă într-o altă secvenţă (copy…)
 Interschimbă valori sau domenii de valori (swap…)
 Transformă o secvenţă sau două secvenţe într-o nouă
secvenţă (transform…)
 Inlocuieşte elementele specificate (replace…)
 Populează un domeniu cu o valoare (fill, fill_n)
 Populează un domeniu cu valori generate (generate,
generate_n)
 Şterge elemente (remove…)
 Şterge duplicate (unique, unique_copy)
 Inversează o secvenţă (reverse, reverse_copy)
 Rotaţia elementelor (rotate, rotate_copy)
 Amestecă aleator elementele (random_suffle)
 Partiţionare cu elemente ce satisfac un predicat (partition,55
stable_partition)
 Sortări şi operaţii asociate
 Sortare domeniu (sort…)
 Plasarea unui element în poziţia în care ar fi în urma sortării
(nth_element)
 Caută limitele sau poziţia unei valori într-o secvenţă sortată
(lower_bound, upper_bound, equal_range,
binary_search)
 Interclasează două secvenţe sortate (merge,
inplace_merge)
 Operaţii de tip Set pe secvenţe sortate (includes, set_union,
set_intersection, set_difference,
set_symmetric_difference)
 Operaţii de tip Heap (push_heap, pop_heap, make_heap,
sort_heap)
 Caută elementul minim sau maxim (min, max,
min_element, max_element)
 Determină ordinea lexicografică a doua secvenţe
(lexicographical_compare)
 Generează permutări pentru o secvenţă (next_permutation, 56
prev_permutation)
 Operaţii numerice generalizate
 Insumează toate elementele unei secvenţe
(accumulate)
 Calculează produsul scalar a două secvenţe
(inner_product)
 Calculează sume parţiale (partial_sum)
 Calculează o nouă secvenţă pornind de la sumele
parţiale în altă secvenţă (adjacent_difference)

57
OBIECTE FUNCŢII (FUNCTORI)
 Reprezintă modalitatea STL de a reprezenta date
executabile

 Pentru anumiţi algoritmi este necesar a se specifica ce


operaţii se vor aplica asupra fiecărui element

 Obiectele funcţii sunt obiecte pentru care este definit cel


puţin operatorul () şi care se comportă ca o funcţie

 Utilizări principale: generare de date, testarea datelor


(predicate) şi aplicarea unor operaţii

https://docs.microsoft.com/en-us/cpp/standard-
library/functional?view=msvc-170 58
Exemplu:
class XYZ{
public:
//definire operator „apel de functie"
return-value operator( ) (arguments) const;
...
};
// definire obiect
XYZ foo;
...
//apel operator( ) pentru obiectul foo
foo(arg1, arg2); // obiect apelat ca o functie
Echivalent cu:
59
foo.operator( ) (arg1, arg2);
FUNCTORI (CONT.)
 Generatoare
 Există algoritmi ce parcurg un domeniu apelând un
obiect funcţie la fiecare pas şi atribuie rezultatul
elementului curent
 Predicate
 Se folosesc pentru a testa anumite condiţii
 Operatorul paranteză () trebuie definit astfel încât să
returneze ceva ce poate fi testat
 Algoritmii ce au în nume sufixul _if folosesc un obiect
funcţie pentru a testa fiecare element pentru o condiţie
 Un predicat dereferenţiază un singur element pentru
teste
 Un predicat binar (BinaryPredicate) dereferenţiază 60
două elemente pentru a le compara
EXEMPLE ALGORITMI

 for_each
template <class InpIter, class Function>
void for_each(InputIter first, InputIter last, Function F);

 Aplică functorul F() pentru a dereferenţia fiecare iterator


din domeniu [first, last).

 Cerinţe:
 InputIter trebuie să aibă cel puţin tipul input
 Functorul F() trebuie să asigure operatorul
operator()(T), unde T este tipul dereferenţiat de
InputIter 61
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

void print (int a) { cout << a << ' '; }


void print_sqr (int a) { cout << a * a << " "; }

int main (){


int numbers[10] = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 };

for_each (numbers, numbers + 10, print);


cout << endl;
vector <int> v1 (10);
for (int i = 0; i < v1.size (); i++)
v1[i] = i;
for_each ( v1.begin(), v1.end(), print_sqr);
cout << endl; cin.get();
62
return 0;
}
 find()
template <class InpIter, class T>
InputIter find(InputIter first, InputIter last, const T& value);
 Returnează un iterator i către primul element din domeniul [first,
last) egal cu valoarea specificată, sau i = last dacă nu există un
astfel de element
 Egalitatea este verificată cu operatorul !=

template <class InputIter, class Predicate>


InputIter find_if(InputIter first, InputIter last, Predicate Pred);
Returnează un iterator i către primul element din domeniul [first,
last) ce face ca predicatul să returneze TRUE (o valoare nenulă),
sau i = last dacă predicatul returnează FALSE pentru toate
elementele din domeniu
 Egalitatea este verificată cu functorul Pred

Cerinţe: - InputIter trebuie să fie cel puţin un iterator de intrare


- Pred() trebuie să fie un functor ce asigură
63
int operator()(S), unde S este tipul derefenţiat de InputIter.
#include <iostream>
#include <algorithm>
using namespace std;

int main(){
int years[ ] = { 1942, 1952, 1962, 1972, 1982, 1992 };
const unsigned yearCount = sizeof (years)/sizeof (years[0]);
int* location = find (years, years + yearCount, 1972);

cout <<"S-a gasit 1972 in pozitia: " <<(location - years + 1)<<endl;

return 0;
}

64
#include <iostream>
#include <algorithm>
using namespace std;

bool odd (int n) { return n % 2; }

int main (){


int numbers[6] = { 2, 4, 8, 15, 32, 64 };

int* location = find_if (numbers, numbers + 6, odd);


if (location != numbers + 6)
cout << "Valoarea " << *location << " din pozitia " << (location-
numbers + 1) << " este impara" << endl;

return 0;
}

65
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool div_3 (int n) { return n % 3 ? 0 : 1; }

int main (){


typedef vector <int> IntVec;
IntVec v (10);

for (int i = 0; i < v.size(); i++)


v[i] = (i + 1) * (i + 1);
IntVec::iterator iter;
iter = find_if (v.begin(), v.end(), div_3);
if (iter != v.end())
cout<< "Valoarea "<< *iter<< " din pozitia "<< (iter - v.begin() +
1)<< " este divizibila cu 3"<< endl;

return 0; 66

}
binary_search():
 template<class FwdIt, class T>

bool binary_search(FwdIt first, FwdIt last, const T& val);

 template<class FwdIt, class T, class Pred>


bool binary_search(FwdIt first, FwdIt last, const T& val,
Pred compare);

Elementele sunt comparate folosind operatorul < pentru


prima varianta și functorul (predicatul) compare pentru a
2-a varianta.
2 elemente a si b sunt considerate egale (echivalente) in
cazurile: if (!(a<b) && !(b<a)),
67
respectiv if (!comp(a,b) && !comp(b,a)).
#include <iostream>
#include <algorithm>
using namespace std;
bool str_compare (const char* a, const char* b){
return ::strcmp (a, b) < 0 ? 1 : 0; // strcmp din spatiul de nume global
}

int main (){


const char* labels[] = { "aa", "dd", "ff", "jj", "ss", "zz" };
const unsigned count = sizeof (labels) / sizeof (labels[0]);
if (binary_search (labels, labels + count, "ff", str_compare))
cout << "\"ff\" este in sir" << endl;
else
cout << "\"ff\: nu este in sir" << endl;

return 0; 68

}
sort():
 template <class RandomAccessIterator>
void sort(RandomAccessIterator first,
RandomAccessIterator last);

 template <class RandomAccessIterator, class Compare>


void sort(RandomAccessIterator first,
RandomAccessIterator last, Compare comp);

 Asigură sortarea elementelor din domeniul [first, last),


 Nu păstrează ordinea elementelor egale

 Prima variantă utilizează operatorul < în timp ce a doua


variantă utilizează functorul pentru comparaţii 69
#include <iostream>
#include <algorithm>
using namespace std;

int array[6] = { 1, 50, -10, 11, 42, 19 };

int main () {
sort (array, array + 6);
for (int i = 0; i < 6; i++)
cout << array[i] << ' ';
cout << endl;
cin.get();
return 0;
} 70
#include <iostream>
#include <algorithm>
#include <functional>
#include<iterator>
using namespace std;
int array[ ] = { 1, 50, -10, 11, 42, 19 };
int main () {
int count = sizeof (array) / sizeof (array[0]);
ostream_iterator <int> iter (cout, " "); // iter afiseaza elementele
cout << "Inainte de sortare: ";
copy (array, array + count, iter); // copiaza fiecare elem unde indica
iter
cout << "\nDupa sortare: ";
sort (array, array + count, greater<int>()); // ultimul param e functorul
copy (array, array + count, iter);
cout << endl;
cin.get(); 71

return 0;
}
#include <vector>
#include <algorithm>
#include <iostream>
#include<iterator>
using namespace std;

int main(){
vector<int> myVec;
vector<int>::iterator item;
ostream_iterator<int> out(cout," ");

// genereaza valori in vector


for ( long i=0; i<10; i++ )
72
myVec.push_back(i);
// amesteca valorile
random_shuffle( myVec.begin(), myVec.end() );
copy( myVec.begin(), myVec.end(), out );
cout << endl;

// sorteaza ascendent vectorul


sort( myVec.begin(), myVec.end() );
copy( myVec.begin(), myVec.end(), out );
cout << endl;
cin.get();
return 0;
}

73
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int main(){
ostream_iterator<int> out(cout," ");
// genereaza valori in vector

int myVec[11];
for ( long i=0; i<10; i++ )
myVec[i] = i;

int * begin = &myVec[0];


int * end = &myVec[10];
74
// amesteca valorile in vector
random_shuffle( begin, end );
copy( begin, end, out );
cout << endl;

// sortare ascendenta a vectorului


sort( begin, end );
copy( begin, end, out );
cout << endl;
cin.get();
return 0;
}

75
 accumulate
 Insumează valorile din domeniu
 template<class InIt, class T> T accumulate(InIt first,
InIt last, T val);
 template<class InIt, class T, class Pred> T
accumulate(InIt first, InIt last, T val, Pred pr);

76
#include <vector>
#include <algorithm>
#include <numeric>
#include <iostream>
using namespace std;
int mult (int initial, int element) { return initial * element; }
int main () {
vector <int> v(5);

for (int i = 0; i < v.size(); i++)


v[i] = i + 1;
int prod = accumulate (v.begin(), v.end(), 1, mult);
cout << "Factorial = " << prod << endl;
cin.get();
return 0;
} // par. 3 – val initiala, mult –function object (val in * elem crt) 77
 generate
 completează o secvenţă folosind o funcţie generatoare
 traversează domeniul [first,… last), şi atribuie fiecărui
element rezultatul execuţei unui functor
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
class Fibonacci {
private:
int v1;
int v2;
public:
Fibonacci () : v1(0), v2(1) { }
int operator () (); 78

};
int Fibonacci::operator() () {
int r = v1 + v2;
v1 = v2;
v2 = r;
return v1;
}
int main (){
vector <int> v1 (10);
Fibonacci generator;

generate_n (v1.begin (), v1.size (), generator);


ostream_iterator<int> iter (cout, " ");
copy (v1.begin (), v1.end (), iter);
cout << endl;
return 0; 79
}

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