Subprogramele (funcțiile) sunt peste tot la BAC informatică: în grilele de la Subiectul I, în itemii scurți de la Subiectul II și, mai ales, la primul item de la Subiectul III, unde trebuie să scrii singur un subprogram. Cea mai mare sursă de greșeli nu e sintaxa, ci un singur caracter: &. În articolul ăsta vezi exact ce face un parametru transmis prin valoare și unul transmis prin referință în C++, și cum urmărești corect un program care le amestecă.
Ce este un subprogram în C++
Un subprogram este o bucată de cod cu nume, pe care o scrii o dată și o apelezi de câte ori ai nevoie. Are un antet (tipul rezultatului, numele, lista de parametri) și un corp între acolade.
#include <iostream>
using namespace std;
int sumaCifre(int n) // antet: returnează un int, are un parametru n
{
int s = 0;
while (n > 0)
{
s = s + n % 10;
n = n / 10;
}
return s; // rezultatul ajunge în locul apelului
}
int main()
{
int x = 2026;
cout << sumaCifre(x) << " " << x; // afișează 10 2026
return 0;
}
Observă că în funcție n ajunge la 0, dar în main variabila x rămâne 2026. Asta e transmiterea prin valoare.
Există două feluri de subprograme:
- funcții cu tip (
int,double,booletc.) — calculează o valoare și o întorc cureturn; apelul se folosește într-o expresie; - funcții
void— nu întorc nimic; fie afișează ceva, fie transmit rezultatele prin parametri de tip referință.
Parametri prin valoare: subprogramul lucrează pe o copie
Când scrii int n în antet, la apel se creează o variabilă nouă, n, care primește o copie a valorii trimise. Orice modificare a lui n rămâne în interiorul subprogramului și dispare la ieșire. De aceea poți „strica” liniștit parametrul (de exemplu să-l împarți la 10 până ajunge la 0) fără să afectezi variabila din main.
Un avantaj: la un parametru prin valoare poți trimite și o expresie sau o constantă: sumaCifre(123), sumaCifre(a + b).
Parametri prin referință: subprogramul lucrează pe original
Când scrii int &n, parametrul nu mai e o copie, ci un alt nume pentru variabila trimisă la apel. Tot ce i se întâmplă lui n în subprogram i se întâmplă de fapt variabilei originale. La apel trebuie să trimiți o variabilă, nu o expresie: f(a + 1) nu compilează dacă parametrul e int &.
Exemplul clasic este interschimbarea:
#include <iostream>
using namespace std;
void schimbaGresit(int a, int b)
{
int aux = a; a = b; b = aux;
}
void schimba(int &a, int &b)
{
int aux = a; a = b; b = aux;
}
int main()
{
int x = 3, y = 7;
schimbaGresit(x, y);
cout << x << " " << y << endl; // 3 7 (nu s-a schimbat nimic)
schimba(x, y);
cout << x << " " << y << endl; // 7 3
return 0;
}
Prima funcție schimbă între ele două copii care dispar imediat. A doua schimbă chiar x și y.
Exemplu de urmărire, ca la BAC
Itemii de grilă combină de obicei ambele tipuri de parametri. Ia programul de mai jos și încearcă să afli singur ce afișează înainte să citești rezolvarea.
#include <iostream>
using namespace std;
void f(int a, int &b)
{
a = a + 1;
b = b + a;
}
int main()
{
int x = 2, y = 3;
f(x, y);
cout << x << " " << y << " ";
f(y, x);
cout << x << " " << y;
return 0;
}
Metoda sigură: notează pentru fiecare apel cine e copie și cine e „alt nume”.
| Moment | a (copie) | b (= variabila) | x | y |
|---|---|---|---|---|
| start | – | – | 2 | 3 |
f(x, y): a ← 2+1 | 3 | b este y | 2 | 3 |
| b ← b + a | 3 | 6 | 2 | 6 |
f(y, x): a ← 6+1 | 7 | b este x | 2 | 6 |
| b ← b + a | 7 | 9 | 9 | 6 |
Programul afișează 2 6 9 6. La al doilea apel rolurile se inversează: y e trimis prin valoare (deci nu se modifică), iar x prin referință. Cine urmărește doar numele din antet, nu și ordinea argumentelor de la apel, greșește aici.
main cu care e legat (de exemplu „b = y”). Așa nu mai uiți să actualizezi variabila originală.Cum întorci mai multe rezultate
Cu return poți întoarce o singură valoare. Dacă enunțul cere două rezultate (de exemplu cea mai mică și cea mai mare cifră a unui număr), folosești parametri prin referință:
#include <iostream>
using namespace std;
void minMaxCifre(int n, int &mn, int &mx)
{
mn = 9; mx = 0;
do
{
int c = n % 10;
if (c < mn) mn = c;
if (c > mx) mx = c;
n = n / 10;
} while (n > 0);
}
int main()
{
int a, b;
minMaxCifre(5182, a, b);
cout << a << " " << b; // 1 8
return 0;
}
Am folosit do … while ca pentru n = 0 să obținem corect mn = mx = 0. Observă și că n e prin valoare: îl distrugem în buclă, dar argumentul de la apel nu e afectat.
Tablourile ca parametri
Un vector transmis ca parametru (int v[]) se comportă ca și cum ar fi transmis prin referință: subprogramul lucrează pe tabloul original, fără să scrii &. Dacă funcția modifică elementele, modificările se văd în main. Numărul de elemente, în schimb, e un int obișnuit; dacă subprogramul șterge sau inserează elemente, trebuie să-l scrii int &n, altfel main nu află noua lungime.
void dubleaza(int v[], int n)
{
for (int i = 1; i <= n; i++)
v[i] = 2 * v[i]; // modifică vectorul original
}
Variabile locale și globale
Variabilele declarate în interiorul unui subprogram sunt locale: există doar cât rulează apelul și nu sunt inițializate automat cu 0. Variabilele declarate în afara oricărei funcții sunt globale, se văd peste tot și sunt inițializate cu 0. Dacă o variabilă locală are același nume ca una globală, în acel subprogram „câștigă” cea locală. La grile apare des capcana în care o funcție modifică o variabilă globală: atunci modificarea rămâne și după apel, exact ca la referință.
Greșeli frecvente
- Ai uitat
&la un parametru prin care trebuie să furnizezi un rezultat: funcția calculează corect, dar rezultatul se pierde. - Ai pus
&fără motiv, iar subprogramul modifică o variabilă pe caremaino folosește mai departe. - O funcție cu tip care nu ajunge la
returnpe toate ramurile (de exemplu doar în interiorul unuiif). - Variabile locale (sume, contoare) neinițializate.
De reținut
- ●
int x= prin valoare: subprogramul primește o copie; modificările nu ies în afară. - ●
int &x= prin referință: parametrul e alt nume pentru variabila de la apel; modificările rămân. - ● La referință trimiți obligatoriu o variabilă, nu o expresie sau o constantă.
- ● Mai multe rezultate se întorc prin parametri de tip referință, de obicei într-o funcție
void. - ● Tablourile ca parametri modifică originalul; lungimea
nare nevoie de&dacă se schimbă. - ● La urmărire, leagă fiecare parametru referință de variabila de la apel, apel cu apel.