Biblioteca
Tutora
BibliotecaInformatică și TIC › clasa a IX-a

Subprograme

Noțiunea de subprogram, funcții și proceduri, parametri formali și actuali, mecanisme de transmitere a parametrilor.

Bacalaureat

Ce este un subprogram și de ce îl folosim

Un subprogram este o porțiune de program cu nume propriu, care rezolvă o sarcină bine delimitată și poate fi apelată ori de câte ori e nevoie. În loc să copiezi de trei ori codul care calculează cel mai mare divizor comun, scrii o singură dată subprogramul cmmdc și îl apelezi de trei ori.

Avantajele care se cer la teorie, dar se simt și în practică:

În C++ subprogramele se numesc generic funcții, dar tradițional se disting două categorii:

Confuzia clasică: o funcție void nu poate apărea într-o expresie aritmetică — nu are ce valoare să ofere.

Antetul funcției: parametri formali și actuali

Antetul unui subprogram declară tot ce trebuie știut pentru a-l apela: tipul rezultatului, numele și lista de parametri. De exemplu: int suma(int a, int b).

Regula de corespondență se aplică pozițional: primul parametru actual ajunge în primul formal, al doilea în al doilea și așa mai departe. Numărul lor trebuie să coincidă, iar tipurile trebuie să fie compatibile. Numele nu contează deloc: parametrul formal se poate numi a, iar cel actual x — nu e nicio legătură de nume între ei, doar de poziție.

Instrucțiunea return expresie; face două lucruri deodată: stabilește valoarea întoarsă și încheie imediat execuția funcției — tot ce urmează după return nu se mai execută. De aici un idiom util: într-o funcție care verifică o proprietate, poți scrie return 0; direct din interiorul buclei, la prima dovadă că proprietatea nu se respectă.

La subiectul III de bacalaureat antetul este de regulă impus — respectarea lui întocmai (tip, nume, ordinea parametrilor) este condiție de punctaj.

Transmiterea prin valoare și prin referință

Mecanismul de transmitere a parametrilor decide dacă subprogramul poate modifica variabilele apelantului.

Când alegi referința? În două situații: când subprogramul trebuie să întoarcă mai mult de un rezultat (return poate întoarce o singură valoare, dar poți avea oricâți parametri prin referință) și când vrei să modifici efectiv argumentul (o procedură de interschimbare: void swap(int &a, int &b)).

Restricție de reținut: prin referință se pot transmite doar variabile — apelul swap(3, 5) e greșit, pentru că 3 și 5 nu sunt zone de memorie modificabile.

Tablourile sunt cazul special: se transmit întotdeauna prin adresă, deci se comportă ca și cum ar fi prin referință — modificările asupra elementelor rămân, fără să scrii vreun &. Este exact invers decât la variabilele simple și este capcana preferată a itemilor de examen.

Variabile locale și globale

Variabilele locale sunt declarate în interiorul unui subprogram (inclusiv parametrii formali): există doar pe durata execuției acelui apel și sunt vizibile doar acolo. Variabilele globale sunt declarate în afara oricărui subprogram: există pe toată durata programului și sunt vizibile din orice funcție.

Reguli care apar în itemii de examen:

Stilul recomandat (și implicit cerut de proiectarea modulară): subprogramele comunică prin parametri și valori returnate, nu prin variabile globale. Excepția pragmatică din practica de bacalaureat: tablourile mari se declară deseori global, ca să nu ocupe stiva.

Recursivitate — primele noțiuni

Un subprogram este recursiv dacă se apelează pe el însuși. Ideea pare circulară, dar funcționează dacă respectă două condiții obligatorii:

Exemplul canonic este factorialul: n! = n (n-1)!, cu cazul de bază 0! = 1. În C++: long f(int n) { if (n == 0) return 1; return n f(n - 1); }. Apelul f(3) declanșează f(2), care declanșează f(1), apoi f(0) — abia acolo lanțul se oprește și rezultatele se compun la întoarcere: 1, 1, 2, 6.

Fiecare apel are propriile variabile locale și propriii parametri, memorate pe stivă; de aceea apelurile nu se încurcă între ele. Fără caz de bază (sau cu o problemă care nu scade), apelurile se adâncesc la nesfârșit și programul se oprește cu depășire de stivă.

Pentru urmărirea execuției — cerință tipică de examen — desenează lanțul apelurilor în jos și compunerea rezultatelor în sus. Atenție la funcțiile care afișează ceva înainte, respectiv după apelul recursiv: ordinea afișărilor se inversează între cele două cazuri, iar acesta este itemul-capcană preferat.

De reținut

subprogram
porțiune de program cu nume propriu, care rezolvă o sarcină delimitată și poate fi apelată ori de câte ori este nevoie
funcție
subprogram care întoarce o valoare prin instrucțiunea return și poate fi folosit ca operand în expresii
procedură (funcție void)
subprogram care nu întoarce o valoare prin numele său; produce efecte precum afișări sau modificarea parametrilor și se apelează ca instrucțiune
parametri formali
parametrii din antetul (definiția) subprogramului, care primesc valori la fiecare apel
parametri actuali (efectivi)
valorile sau variabilele scrise la apelul subprogramului, puse în corespondență pozițională cu parametrii formali
transmitere prin valoare
mecanism în care parametrul formal primește o copie a valorii; modificările din subprogram nu afectează variabila apelantului
transmitere prin referință
mecanism (marcat cu & în C++) în care parametrul formal este un alt nume al variabilei originale; modificările rămân după apel
variabilă locală
variabilă declarată în interiorul unui subprogram, care există doar pe durata apelului și este vizibilă doar în acel subprogram
variabilă globală
variabilă declarată în afara subprogramelor, vizibilă din tot programul; cele numerice sunt inițializate automat cu 0
subprogram recursiv
subprogram care se apelează pe el însuși; corectitudinea cere un caz de bază și apeluri pe probleme din ce în ce mai mici

Greșeli frecvente

Greșit: O funcție void ar putea fi folosită într-o expresie: s = f(x) + 1 cu f de tip void
Corect: Funcțiile void nu întorc nicio valoare prin nume, deci nu pot apărea ca operand; doar funcțiile cu tip de rezultat (int, double etc.) se folosesc în expresii
Greșit: Parametrii actuali s-ar potrivi cu cei formali după nume
Corect: Corespondența este strict pozițională: primul actual ajunge în primul formal, indiferent de nume; două variabile cu același nume în apelant și în subprogram sunt entități diferite
Greșit: Modificarea unui parametru transmis prin valoare s-ar vedea și la apelant
Corect: Prin valoare subprogramul lucrează pe o copie: variabila apelantului rămâne neschimbată; ca modificarea să rămână, parametrul trebuie declarat prin referință (int &x)
Greșit: După instrucțiunea return s-ar mai executa restul funcției
Corect: return încheie imediat execuția funcției; instrucțiunile de după el (pe aceeași ramură) nu se mai execută niciodată — de aceea un return pus greșit într-o buclă oprește căutarea prea devreme
Greșit: O funcție recursivă fără caz de bază s-ar opri singură când n devine mic
Corect: Fără caz de bază apelurile continuă la nesfârșit până la depășirea stivei; oprirea trebuie programată explicit printr-o condiție care întoarce rezultatul fără alt apel recursiv

Test — 6 întrebări ca la examen

1. În definiția int putere(int b, int e), b și e sunt:
  1. parametri actuali
  2. parametri formali
  3. variabile globale
  4. constante
Vezi răspunsul
parametri formali. Parametrii din antetul definiției sunt formali; ei primesc valori abia la apel. Parametrii actuali sunt cei scriși la apel, de exemplu putere(2, 10) — confuzia dintre cele două denumiri este itemul de teorie cel mai des întâlnit.
2. Se definește void f(int x) { x = x * 2; }. În programul principal: a = 5; f(a);. Ce valoare are a după apel?
  1. 10
  2. 5
  3. 0
  4. nedefinită
Vezi răspunsul
5. x este transmis prin valoare, deci f lucrează pe o copie a lui a; dublarea copiei nu atinge originalul, iar a rămâne 5. Răspunsul 10 ar fi corect doar dacă antetul ar fi void f(int &x) — cu transmitere prin referință.
3. Care antet este potrivit pentru un subprogram care interschimbă valorile a două variabile întregi?
  1. void swap(int a, int b)
  2. int swap(int a, int b)
  3. void swap(int &a, int &b)
  4. int swap(int &a, int b)
Vezi răspunsul
void swap(int &a, int &b). Interschimbarea trebuie să modifice ambele variabile ale apelantului, deci ambii parametri se transmit prin referință. Primul antet — capcana clasică — compilează fără eroare, dar interschimbă doar copiile: la revenire variabilele originale rămân neschimbate.
4. O variabilă globală int n; neinițializată explicit și o variabilă locală int m; neinițializată au valorile:
  1. amândouă 0
  2. n = 0, m nedefinită
  3. amândouă nedefinite
  4. n nedefinită, m = 0
Vezi răspunsul
n = 0, m nedefinită. Variabilele globale numerice sunt inițializate automat cu 0; cele locale primesc o zonă de memorie cu valoare reziduală, deci nedefinită până la prima atribuire. Varianta amândouă 0 e tentantă pentru că unele compilatoare par să dea 0 local — dar limbajul nu garantează asta.
5. Pentru long f(int n) { if (n == 0) return 1; return n * f(n - 1); }, câte apeluri ale funcției f se execută în total pentru f(4), inclusiv apelul inițial?
  1. 4
  2. 5
  3. 24
  4. 3
Vezi răspunsul
5. Se apelează f(4), f(3), f(2), f(1) și f(0) — cinci apeluri, ultimul fiind cazul de bază. Varianta 4 uită tocmai apelul f(0), iar 24 este valoarea returnată (4!), nu numărul de apeluri — distractorul care testează dacă ai citit întrebarea.
6. Se definește void g(int n) { if (n > 0) { g(n - 1); cout << n; } }. Ce afișează apelul g(3)?
  1. 321
  2. 123
  3. 333
  4. nimic
Vezi răspunsul
123. Afișarea este pusă DUPĂ apelul recursiv, deci se execută la întoarcere: mai întâi coboară g(3) → g(2) → g(1) → g(0), apoi afișează în ordinea revenirii 1, 2, 3. Varianta 321 ar fi corectă dacă afișarea ar sta înaintea apelului — exact inversarea pe care o testează acest tip de item.
Deschide varianta interactivă — cu AI care îți explică
← Structuri de dateMetode de programare – introducere →
BiologieChimieEconomieFilosofieFizicăGeografieInformatică și TICIstorieLogică și argumentareMatematicăPsihologieLimba și literatura română