Stim ca la transmiterea prin valoare a argumentelor unei
functii, prelucrarea nu se face asupra variabilelor-argument, ci asupra unor
copii. Copiile sunt variabile interne blocului functiei, de aceea durata lor de
viata este limitata la blocul functiei. Reguli:
1) nu folosim transmisia prin valoare in cazul unor
variabile de mari dimensiuni, pentru ca dimensiunea trebuie replicata pentru
copiile folosite in corpul functiei;
2) nu folosim transferul prin valoare cand vrem ca
variabilele transmise ca argument sa fie modificate.
Pentru cazurile mentionate anterior putem folosi transferul
prin referinta (alias al variabilei-argument, permis doar in C++) sau
transferul prin adresa (cu pointeri, permis in C/C++).
Transferul prin adresa
este, de fapt, tot un transfer prin valoare.
Ce se transfera in parametrul functiei este o copie a pointerului. O copie a pointerului indirecteaza aceeasi variabila (sau, altfel spus, un pointer-copie are drept valoare aceeasi adresa de variabila ca pointerul "original"). Modificarea variabilei indirectate de pointerul-copie in corpul (blocul) functiei se face in urma aplicarii operatorului de dereferentiere. Dereferentierea copiei unui pointer da acelasi rezultat ca dereferentierea originalului. Nu e nimic mistic aici, suntem, in mod absolut formal, tot in cazul transferului prin valoare :-)
Ce se transfera in parametrul functiei este o copie a pointerului. O copie a pointerului indirecteaza aceeasi variabila (sau, altfel spus, un pointer-copie are drept valoare aceeasi adresa de variabila ca pointerul "original"). Modificarea variabilei indirectate de pointerul-copie in corpul (blocul) functiei se face in urma aplicarii operatorului de dereferentiere. Dereferentierea copiei unui pointer da acelasi rezultat ca dereferentierea originalului. Nu e nimic mistic aici, suntem, in mod absolut formal, tot in cazul transferului prin valoare :-)
Revenind la referinte si la transferul lor ca parametri
de functie, tipul de return, daca exista, poate fi o valoare, o referinta sau
un pointer, cu specificarea, in ultimele doua cazuri, ca nu putem returna
referinta sau adresa unei variabile locale functiei, deoarece aceasta isi
inceteaza existenta odata cu iesirea din blocul functiei.
In exemplul urmator, este definita o functie de alocare
dinamica a unui tablou, pe care o apelam din functia main. Atribuim rezultatul
unui pointer (cu tip corespunzator). Initializam si efectuam prelucrari asupra
elementelor tabloului, de ex. depozitam in fiecare element de index i suma
numerelor naturale pana la i, inclusiv. La final, afisam tabloul si eliberam
spatiul ocupat de tablou.
Remarca importanta: spatiile de memorie alocate dinamic
NU au durata de viata, precum au variabilele locale ale unei functii oarecare.
De aceea, memoria alocata dinamic trebuie elibarata manual.
//Functii cu
transferul argumentelor prin valoare/adresa
#include <iostream>
using namespace std;
int* CreezTablou(int);
void PrelucrezTablou(int*, int);
void AfisezTablou(int*, int);
int main()
{
int n;
cout <<
"\nDimensiunea tabloului n = ";
cin >> n;
int* q =
CreezTablou(n);
PrelucrezTablou(q, n);
AfisezTablou(q,
n);
delete [] q;
return 0;
}
int* CreezTablou(int n)
{
return new int
[n];
}
void PrelucrezTablou(int* p, int n)
{
for(int i = 0;
i < n; i++)
{
*(p + i)
= 0;
// echivalent: p[i] = 0; oricarui
pointer i se poate aplica operatorul []
for (int
j = 1; j <= i; j++) *(p + i) += j;
}
}
// am initializat elementele din p[i] cu suma elementelor pana la indecsii i
// am initializat elementele din p[i] cu suma elementelor pana la indecsii i
void AfisezTablou(int* p, int n)
{
cout <<
"\n_________________________\n";
for(int i = 0;
i < n; i++)
{
cout
<< "p[" << i << "] = " << *(p + i)
<< "\n";
}
}
--------------------------------------------------------
Valori
implicite in prototipul unei functii
int Volum( int lungime, int latime, int inaltime = 10);
// definitie Volum
/..../
int Volum ( int lungime, int latime, int inaltime = 10)
//declaratie Volum
{
return
lungime*latime*inaltime;
}
Reguli:
1) Apelul Volum(10, 10) returneaza 1000. Apelul Volum(10,
10, 10) returneaza 1000. Apelul (10, 10, 1) returneaza 100, altfel spus, transferul
tuturor argumentelor in parametrii formali are intaietate in cazul in care
exista parametri cu valoare implicita.
2) Daca in prototipul unei functii apare un parametru cu
valoare implicita, eventualii parametri care urmeaza in prototip trebuie, de
asemenea, sa aiba specificate valori implicite. Altfel spus, parametrii cu valori
implicite sunt ultimii in prototipul unei functii.
Ex: int Volum1(int lungime, int latime = 10, int inaltime
= 11);
C++ permite supraincarcarea functiilor sau crearea unor
functii cu acelasi nume dar cu tip return si parametri diferiti. Ex:
int Multiply(int x, int y);
double Multiply(double z, double w);
Functii
inline
Cuvantul inline specificat in fata definitiei unei
functii produce urmatorul efect: cand numele functiei este intalnit in cod, nu
se mai realizeaza apelul, ci pur si simplu este copiata functia respectiva in
locul unde ar trebui sa fie apelata.
Acesta este un mecanism de optimizare,
care elimina apelul de functie (procesarea se face local) si este recomandat
doar pentru functiile de mici dimensiuni, cum este functia Volum din exemplul
anterior.
Alt exemplu: operatii cu numere complexe folosind structuri
si transfer prin referinta
// Adunarea, scaderea si conjugatul numerelor complexe
#include <iostream>
using namespace std;
struct Complex
{
double _re;
double _im;
};
void Ad(const Complex &c1, const Complex &c2,
Complex &c3);
// primele doua argumente nu sunt modificate de functie
// de aceea parametrii formali corespunzatori sunt referinte constante
// de aceea parametrii formali corespunzatori sunt referinte constante
void Dif(const Complex &c1, const Complex &c2,
Complex &c3);
void Conj(const Complex &c1, Complex &c2);
void Show(Complex c);
int main()
{
Complex c1, c2,
c3, c4;
cout <<
"\nPartea reala primul numar = ";
cin >>
c1._re;
cout <<
"\nPartea imaginara primul numar = ";
cin >>
c1._im;
cout <<
"\nPartea reala al doilea numar = ";
cin >>
c2._re;
cout <<
"\nPartea imaginara al doilea numar = ";
cin >>
c2._im;
Ad(c1, c2, c3);
Show(c3);
Dif(c1, c2,
c4);
Show(c4);
Conj(c1, c3);
Show(c3);
Conj(c2, c4);
Show(c4);
return
0;
}
void Ad(const Complex &a, const Complex &b,
Complex &c)
{
c._re = a._re
+ b._re;
c._im = a._im
+ b._im;
}
void Dif(const Complex &a, const Complex &b,
Complex &c)
{
c._re = a._re
- b._re;
c._im = a._im
- b._im;
}
void Conj(const Complex &a, Complex &c)
{
c._re = a._re;
c._im =
-a._im;
}
void Show(Complex d)
{
cout <<
"\n" << d._re;
if(d._im >=
0)
{
cout
<< " + " << "i*(" << d._im <<
").";
}
else
{
cout
<< " - " << "i*(" << -d._im <<
").";
}
}
-------------------------------------------------------
Apelul
recursiv
O functie poate primi ca argument valoarea de return a
functiei insasi, adica se poate autoapela. Pentru ca numarul de apelari sa fie
finit trebuie impusa o conditie corespunzatoare de stop in definitia functiei.
De pilda, in cazul functiei factorial se poate impune
conditia ca apelurile sa se opreasca la valoarea 2. In cazul in care valorile
permise sunt inferioare lui 2 (adica 0 sau 1) functia returneaza 1, conform
definitiei factorialului: 1! = 1 si 0! = 1.
//Factorial Recursiv
int RecFactor(int n)
{
if(n < 2)
return 1;
return
n*RecFactor(n - 1);
}
.....
Sa presupunem, pentru claritate, ca vrem sa calculam
factorial de 4. Valoarea ceruta nu se poate returna in aceasta etapa dar
putem expanda expresia:
return 4*RecFactor(3) <==> 4*(3*RecFactor(2))
<==> 4*3*(2*RecFactor(1))
<==> 4*3*2*1.
Dupa apelul initial, au loc trei autoapeluri ale
RecFactor(n), pentru n = 3, 2, 1. Conditia fixata pentru n < 2 impiedica
autoapelurile in continuare.
Codul urmator implementeaza apelul recursiv pentru
calculul numerelor Fibonacci, date, dupa cum se stie, de f(n) = f(n - 1) + f(n
- 2), cu f(1) = 1 si f(2) = 1.
// Fibonacci recursiv
#include <iostream>
using namespace std;
int RecFib(int);
void Show(int);
int main()
{
int n;
cout <<
"Numere Fibonacci pana la n = ";
cin >> n;
Show(n);
return 0;
}
int RecFib(int n)
{
if(n == 1 || n
== 2)
{
return 1;
}
else
{
return
(RecFib(n - 1) + RecFib(n - 2));
}
}
void Show(int n)
{
cout <<
"\n________________________\n";
for(int i = 1;
i <= n; i++)
{
cout << "\nAl "
<< i <<"-lea nr. Fibonacci este " << RecFib(i);
}
}
La fel ca in cazul factorialului, expresia return
RecFib(n - 1) + RecFib(n - 2) este expandata prin autoapelare pana la
intalnirea valorilor de stop, adica 1 si 2.
Merita reamintita, cu aceasta ocazie, implementarea nerecursiva a numerelor Fibonacci.
//Fibonacci nerecursiv
#include <iostream>
using namespace std;
int FibNum(int);
void Show(int);
int main()
{
cout <<
"\nNumere Fibonacci pana la n = ";
int n;
cin >> n;
Show(n);
return 0;
}
int FibNum(int n)
{
if(n == 1 || n
== 2) return 1;
int a = 1;
int b = 1;
int rez;
for(int i = 3;
i <= n; i++)
{
rez = b +
a;
a = b;
b = rez;
}
return rez;
}
void Show(int n)
{
cout <<
"\n______________________\n";
for(int i = 1;
i <= n; i++)
{
cout
<< "\nAl " << i << "-lea numar Fibonacci:
" << FibNum(i);
}
}
La prima vedere, implementarea nerecursiva pare sa aiba
codul un pic mai "stufos". La apel se vede insa adevarata diferenta.
Fibonacci nerecursiv afiseaza cvasi-instantaneu primele
45 de numere (ultimul rezultat permis in domeniul intregilor cu semn este
pentru n = 46) dar Fibonacci recursiv are nevoie de zeci de secunde (!!!) pentru a obtine acelasi
rezultat pe un sistem P6100/2 GHz la 4 GB memorie instalata.
No comments:
Post a Comment