Vreau sa inteleg exact cum ruleaza programul meu!
Obiectivul lectiei
Cand ai mai multe functii, programul "sare" de la una la alta. Cum stii exact ce se intampla si in ce ordine?
Dupa aceasta lectie vei putea:
- Sa explici cum functioneaza apelarea unei functii
- Sa explici exemplu simplu: main cheama o functie
- Sa aplici ce este call stack-ul
- Sa construiesti comportament complex din piese simple
- [Optional/Avansat] Sa recunosti ca o functie poate sa se apeleze pe ea insasi (recursivitate)
Incearca singur!
Urmareste executia!
Copiaza codul, ruleaza-l si observa ordinea in care se afiseaza mesajele. Apoi modifica-l.
void introducere() { cout << "Ma numesc Robot!" << endl; } si cheam-o din conversatie() intre salutare() si despartire(). Ce se schimba?
salutare() dar sa o lasi apelata in conversatie(). Ce eroare primesti?
int dublu(int x) care returneaza x*2 si o functie int triplu(int x) care returneaza x*3. Apoi afiseaza dublu(triplu(2)). Ce rezultat obtii?
Urmareste regulile simple:
1. Programul incepe INTOTDEAUNA din main()
2. Cand intalneste un apel de functie, sare la acea functie
3. Cand functia se termina, se intoarce exact de unde a plecat
Deci: main → conversatie → salutare → (inapoi in conversatie) → despartire → (inapoi in conversatie) → (inapoi in main)
Compozitia functioneaza din interior spre exterior:
dublu(triplu(2)) → mai intai se calculeaza triplu(2) = 6
Apoi se calculeaza dublu(6) = 12
Rezultat: 12
Cum functioneaza apelarea unei functii?
Cand programul intalneste un apel de functie (de ex. salutare();), se intampla 3 lucruri:
1. Programul pune un semn unde a ramas (isi noteaza pozitia curenta)
2. Sare la codul functiei apelate si il executa linie cu linie
3. Cand functia se termina, se intoarce exact la locul marcat si continua de acolo
Citesti o carte (main). La pagina 50 scrie "vezi Anexa B". Pui un semn de carte la pagina 50, mergi la Anexa B, o citesti, apoi te intorci la pagina 50 si continui. Daca in Anexa B scrie "vezi Nota 3", pui un ALT semn de carte in Anexa B, mergi la Nota 3, o citesti, te intorci in Anexa B, termini, si apoi te intorci la pagina 50.
Exemplu simplu: main cheama o functie
Sa urmarim exact ce se intampla cand rulam acest program:
| Pas | Ce se executa | Output | Unde suntem |
|---|---|---|---|
| 1 | Linia 1: cout << "Inainte" | Inainte | in main() |
| 2 | Linia 2: salut(); → SALT! | - | main() → salut() |
| 3 | Linia B: cout << "Salut!" | Salut! | in salut() |
| 4 | salut() se termina → INAPOI | - | salut() → main() |
| 5 | Linia 3: cout << "Dupa" | Dupa | in main() |
Output final: Inainte apoi Salut! apoi Dupa
Ce este Call Stack-ul?
Calculatorul tine o stiva (stack) cu functiile active. Cand apelezi o functie, o pune deasupra stivei. Cand functia se termina, o scoate de pe stiva. Functia din varful stivei este cea care ruleaza acum.
Gandeste-te la o stiva de farfurii: pui o farfurie deasupra (apel) si o scoti de deasupra (return). Ultima pusa = prima scoasa.
Construim comportament complex din piese simple
Puterea functiilor sta in faptul ca le poti combina. O functie poate chema oricata alta functie. Asta iti permite sa construiesti programe complexe din bucati mici, usor de inteles.
O functie poate sa se apeleze pe ea insasi! [concept avansat — optional]
Recursivitatea este un concept care se preda de obicei in clasa a XI-a la Informatica de liceu, dupa ce stapanesti bine iteratia (for/while) si functiile de baza. Daca abia ai invatat functiile, este normal sa ti se para greu. Poti sari acest atom si sa revii mai tarziu — nu afecteaza restul lectiei.
Recursivitatea este tehnica prin care o functie se cheama pe sine insasi. Functia rezolva o problema mare, descompunand-o intr-un caz mai mic, pana cand ajunge la un caz de baza (conditia de oprire).
Cele doua reguli obligatorii ale recursiei:
1. Conditia de oprire (cazul de baza) — o verificare care, cand e adevarata, opreste recursivitatea si intoarce direct un rezultat.
2. Apelul recursiv — functia se cheama pe sine cu un argument mai mic/simplu, care se apropie de conditia de oprire.
Inainte de a vedea codul, citeste traseul executiei pentru numaraInvers(3):
Apel 1: n=3. Este 3<=0? NU. Afiseaza "3...". Cheama numaraInvers(2).
Apel 2: n=2. Este 2<=0? NU. Afiseaza "2...". Cheama numaraInvers(1).
Apel 3: n=1. Este 1<=0? NU. Afiseaza "1...". Cheama numaraInvers(0).
Apel 4: n=0. Este 0<=0? DA! Afiseaza "START!" si se opreste.
Output final: 3... 2... 1... START!
Apel 1
Apel 2
Apel 3
Apel 4 (STOP)
La n=0, conditia de oprire se activeaza. Stiva incepe sa se goleasca: fiecare functie se intoarce la cea de dedesubt.
Daca uiti conditia de oprire, functia se cheama pe sine la infinit. Stiva creste si creste pana cand... CRASH! Eroarea se numeste "stack overflow" (stiva a dat pe-afara). Programul se opreste fortat.
Rezultatul unei functii ca argument pentru alta
Poti folosi valoarea returnata de o functie direct ca argument pentru alta functie. Se evalueaza de la interior spre exterior (ca in matematica).
Pasul 1: Se evalueaza triplu(2) = 2 * 3 = 6
Pasul 2: Rezultatul (6) devine argument: dublu(6) = 6 * 2 = 12
Fiecare functie are propriile variabile
Variabilele declarate in interiorul unei functii sunt locale - ele exista DOAR in acea functie. Cand functia se termina, variabilele dispar. Alte functii nu le pot accesa.
Regula: Defineste inainte de a apela
In C++, compilatorul citeste codul de sus in jos. Daca apelezi o functie care nu a fost inca definita, primesti eroare. Solutia: scrie functia deasupra lui main, sau foloseste un prototip.
Prototipurile sunt utile cand ai mai multe functii care se cheama reciproc, sau cand vrei ca main() sa fie primul lucru pe care il citesti in fisier. In programe mici, e suficient sa definesti functiile deasupra lui main.
Exercitii practice
Exercitiul 1 (Nivel minim) - Traseaza executia
Analizeaza urmatorul program si raspunde la intrebari:
void gamma() { cout << "G "; }
void beta() { cout << "B1 "; gamma(); cout << "B2 "; }
void alpha() { cout << "A1 "; beta(); cout << "A2 "; beta(); cout << "A3 "; }
int main() { alpha(); }
- Scrie pe hartie, pas cu pas, ordinea exacta in care se afiseaza mesajele. Care este output-ul complet?
- De cate ori apare "G " in output? De ce?
- Deseneaza call stack-ul in momentul in care se executa
gamma()pentru prima data. Cate "etaje" are stiva?
Raspunde numerotat: 1. ... 2. ... 3. ...
Exercitiul 2 (Nivel standard) - Compozitie de functii
Cerinta: Scrie 3 functii: int patrat(int x) care returneaza x*x, int succ(int x) care returneaza x+1, si int dublu(int x) care returneaza x*2. Apoi in main afiseaza rezultatele urmatoarelor compozitii:
De calculat:
a) patrat(succ(3))
b) dublu(patrat(2))
c) succ(dublu(patrat(1)))
d) patrat(patrat(2))
Indicii:
- Evalueaza din interior spre exterior
- a) succ(3) = 4, patrat(4) = 16
- Verifica raspunsurile ruland programul!
Exercitiul 3 (Nivel performanta) - Scope si debugging
Cerinta: Urmatorul program are 3 greseli legate de scope si apelare. Gaseste-le, explica de ce sunt greseli si corecteaza-le:
void calcul() {
int rezultat = 42;
}
int aduna(int a, int b) {
return a + b;
}
int main() {
calcul();
cout << rezultat;
int s = aduna(5);
cout << s;
}
Indicii:
- Cauta variabile folosite in afara scope-ului lor
- Verifica daca numarul de argumente corespunde cu numarul de parametri
- Gandeste-te cum poti "scoate" un rezultat dintr-o functie void
Cuvinte cheie de folosit: scope, variabila locala, parametru, argument, return, apel, prototip, call stack
Ce ai invatat astazi
- Ai invatat cum functioneaza apelarea unei functii
- Acum stii exemplu simplu: main cheama o functie
- Ai descoperit ce este call stack-ul
- Ai explorat construim comportament complex din piese simple
- [Optional/Avansat] Ai descoperit ca o functie poate sa se apeleze pe ea insasi (recursivitate — concept de liceu)
- Ai invatat rezultatul unei functii ca argument pentru alta
- Acum stii fiecare functie are propriile variabile
- Ai descoperit regula: defineste inainte de a apela