Un program C/C++ este o colecție de module distincte numite funcții. Împărțirea codului în funcții mici, fiecare cu o responsabilitate clară, este principalul instrument de control al complexității: face codul reutilizabil, testabil și mult mai ușor de citit.
1Obiectivele lucrării
- Scrierea definițiilor și a prototipurilor de funcții
- Distingerea transmiterii prin valoare de cea prin adresă
- Înțelegerea mecanismului stivei de apel
- Aplicarea corectă a domeniilor de vizibilitate
- Implementarea funcțiilor recursive și recunoașterea limitelor lor
- Folosirea supraîncărcării și a parametrilor impliciți din C++
2Definiție și prototip
sintaxa definiției
tip_rez nume_functie (lista_parametri)
{
<declaratii locale>
secventa de instructiuni
}
| Element | Rol | Observații |
|---|---|---|
tip_rez | tipul valorii returnate | implicit int; se scrie void dacă nu se returnează nimic |
nume_functie | identificator | respectă regulile identificatorilor |
lista_parametri | declarații de forma tip nume | se separă prin virgulă; void sau gol dacă nu există |
prototip vs. definiție
// PROTOTIP (declaratie) - spune compilatorului ce sa astepte
double aria(double raza); // se pune inainte de main, in zona globala
int main(void) {
printf("%.2f\n", aria(2.5)); // apelul este acum valid
return 0;
}
// DEFINITIE - corpul propriu-zis, poate fi si in alt fisier
double aria(double raza) {
return 3.14159 * raza * raza;
}
De ce este nevoie de prototipCompilatorul citește fișierul de sus în jos.
Fără prototip, la întâlnirea apelului nu știe câți parametri are funcția și de ce tip, deci nu poate
verifica corectitudinea. Prototipul rezolvă problema fără a impune o anumită ordine a definițiilor.
Funcțiile nu se pot imbricaÎn C/C++, spre deosebire de Pascal, o funcție
nu poate fi definită în interiorul alteia. Toate definițiile sunt la același nivel.
3Transmiterea parametrilor
| Mod | Ce primește funcția | Modifică originalul? | Cost |
|---|---|---|---|
| Prin valoare | o copie a valorii | nu | copierea datelor |
| Prin pointer | adresa variabilei | da | doar o adresă |
| Prin referință (C++) | un alias al variabilei | da | doar o adresă |
| Tablou | întotdeauna adresa primului element | da | doar o adresă |
Tablourile fac excepțieUn tablou nu se copiază niciodată la apel. Chiar dacă
parametrul este scris
int v[], funcția primește un pointer, deci modificările
se reflectă în tabloul original. Din același motiv, sizeof aplicat parametrului dă
dimensiunea unui pointer, nu a tabloului - dimensiunea trebuie transmisă separat.4Simulator: stiva de apel
Urmăriți ce se întâmplă cu variabilele când se apelează o funcție: se creează copii pe stivă, iar la revenire acestea dispar. Observați de ce prima variantă nu modifică nimic.
5Domeniu de vizibilitate
| Tip de variabilă | Vizibilă în | Trăiește |
|---|---|---|
| Locală (automată) | blocul în care e declarată | până la ieșirea din bloc |
| Parametru formal | corpul funcției | pe durata apelului |
Locală static | blocul în care e declarată | tot programul |
| Globală | tot fișierul, după declarație | tot programul |
Mascarea numelorO variabilă locală cu același nume ca una globală o
„ascunde" în interiorul blocului. Globala rămâne accesibilă în C++ prin operatorul de scop:
::nume.Evitați variabilele globaleEle pot fi modificate din orice punct al programului,
ceea ce face imposibilă urmărirea sursei unei erori. Preferați transmiterea explicită prin parametri.
6Recursivitate
O funcție recursivă se apelează pe ea însăși. Orice funcție recursivă trebuie să aibă:
- un caz de bază, care se rezolvă direct, fără apel recursiv;
- un pas recursiv care se apropie de cazul de bază la fiecare apel.
Fără caz de bază → stack overflowFiecare apel consumă spațiu pe stivă.
Dacă recursivitatea nu se oprește, stiva se epuizează și programul se închide brutal.
| Problemă | Recursiv | Iterativ |
|---|---|---|
| Factorial | elegant, dar consumă stivă | mai eficient |
| Fibonacci naiv | foarte lent - O(2ⁿ), recalculează aceleași valori | O(n) |
| Parcurgerea arborilor | natural și clar | necesită stivă explicită |
| Turnurile din Hanoi | soluția evidentă | complicat |
7Facilități suplimentare în C++
| Facilitate | Descriere | Exemplu |
|---|---|---|
| Supraîncărcare | mai multe funcții cu același nume, dar parametri diferiți | int max(int,int) și double max(double,double) |
| Parametri impliciți | valori folosite dacă argumentul lipsește la apel | void f(int a, int b = 10) |
Funcții inline | codul se inserează la locul apelului | inline int patrat(int x) |
| Referințe | transmitere fără copiere, cu sintaxă simplă | void f(int &x) |
Reguli pentru supraîncărcareFuncțiile trebuie să difere prin numărul sau tipul
parametrilor. Diferența doar prin tipul returnat nu este suficientă - compilatorul nu ar putea alege.
Parametrii impliciți se pun doar la sfârșitul listei.
8Cod sursă
functii_baza.c - prototipuri și apeluri
#include <stdio.h>
// prototipuri
double aria(double raza);
int maxim(int a, int b);
void afiseazaLinie(char c, int n);
void minMax(int v[], int n, int *min, int *max);
int main(void)
{
afiseazaLinie('=', 40);
printf("Aria pentru r=2.5: %.4f\n", aria(2.5));
printf("Maximul dintre 17 si 42: %d\n", maxim(17, 42));
int v[] = {29, 10, 14, 37, 5};
int mn, mx;
minMax(v, 5, &mn, &mx); // doua rezultate prin pointeri
printf("Minim=%d Maxim=%d\n", mn, mx);
afiseazaLinie('=', 40);
return 0;
}
double aria(double raza) { return 3.14159265 * raza * raza; }
int maxim(int a, int b) { return (a > b) ? a : b; }
void afiseazaLinie(char c, int n)
{
for (int i = 0; i < n; i++) putchar(c);
putchar('\n');
}
void minMax(int v[], int n, int *min, int *max)
{
*min = *max = v[0];
for (int i = 1; i < n; i++) {
if (v[i] < *min) *min = v[i];
if (v[i] > *max) *max = v[i];
}
}
recursivitate.c - comparație cu varianta iterativă
#include <stdio.h>
long factorialRec(int n)
{
if (n <= 1) return 1; // CAZ DE BAZA - obligatoriu
return n * factorialRec(n - 1); // pas recursiv
}
long factorialIter(int n)
{
long r = 1;
for (int i = 2; i <= n; i++) r *= i;
return r;
}
long fibonacciRec(int n) // ATENTIE: exponential de lent
{
if (n <= 1) return n;
return fibonacciRec(n - 1) + fibonacciRec(n - 2);
}
long fibonacciIter(int n) // liniar, mult mai rapid
{
long a = 0, b = 1, t;
for (int i = 0; i < n; i++) { t = a + b; a = b; b = t; }
return a;
}
int cmmdc(int a, int b) // algoritmul lui Euclid
{
if (b == 0) return a;
return cmmdc(b, a % b);
}
int main(void)
{
printf("10! recursiv = %ld\n", factorialRec(10));
printf("10! iterativ = %ld\n", factorialIter(10));
printf("Fibonacci(20) = %ld\n", fibonacciIter(20));
printf("cmmdc(48, 18) = %d\n", cmmdc(48, 18));
return 0;
}
supraincarcare.cpp - facilități C++
#include <iostream>
using namespace std;
// aceeasi denumire, parametri diferiti
int maxim(int a, int b) { return (a > b) ? a : b; }
double maxim(double a, double b) { return (a > b) ? a : b; }
int maxim(int a, int b, int c) { return maxim(maxim(a, b), c); }
// parametri impliciti - doar la sfarsitul listei
void afiseaza(const char *text, int repetari = 1, char separator = '\n')
{
for (int i = 0; i < repetari; i++)
cout << text << separator;
}
// transmitere prin referinta
void dubleaza(int &x) { x *= 2; }
int main()
{
cout << maxim(3, 7) << endl; // varianta int
cout << maxim(3.5, 7.1) << endl; // varianta double
cout << maxim(3, 7, 5) << endl; // varianta cu trei parametri
afiseaza("Salut"); // foloseste valorile implicite
afiseaza("Test", 3); // repeta de 3 ori
afiseaza("A", 4, ' '); // separator personalizat
cout << endl;
int n = 21;
dubleaza(n);
cout << "n = " << n << endl; // 42
return 0;
}
9Atelier de cod
La funcții, panoul din dreapta arată stiva de apeluri: fiecare apel își are propriul set de variabile. La recursivitate se vede cum se adună cadrele unul peste altul.
De încercatRulați pas cu pas și opriți-vă când sunt afișate mai multe cadre
factorial() unul sub altul: fiecare are propriul n. Ștergeți condiția de oprire și veți vedea eroarea de stivă plină.10Sarcini de lucru
- Scrieți funcții pentru aria și perimetrul cercului, dreptunghiului și triunghiului.
- Implementați o funcție care returnează simultan minimul și maximul unui vector, prin pointeri.
- Verificați experimental că o funcție cu parametru transmis prin valoare nu modifică originalul.
- Scrieți factorialul recursiv și iterativ; comparați rezultatele pentru n = 20.
- Măsurați timpul de execuție pentru Fibonacci recursiv și iterativ, la n = 40.
- Implementați cel mai mare divizor comun prin algoritmul lui Euclid, recursiv.
- Scrieți o funcție care primește un tablou și afișați
sizeofîn interiorul ei - explicați rezultatul. - În C++, supraîncărcați o funcție de calcul al mediei pentru 2, 3 și 4 argumente.
11Aplicație de aprofundare
ExtindereImplementați problema Turnurilor din Hanoi cu afișarea fiecărei mutări
și numărarea lor. Verificați experimental formula 2ⁿ − 1. Adăugați apoi un contor global al adâncimii
de recursivitate și afișați adâncimea maximă atinsă. Comparați numărul de apeluri pentru Fibonacci
recursiv naiv cu varianta memoizată (care reține rezultatele deja calculate într-un tablou).