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:

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”.

Momenta (copie)b (= variabila)xy
start––23
f(x, y): a ← 2+13b este y23
b ← b + a3626
f(y, x): a ← 6+17b este x26
b ← b + a7996

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.

La BAC: când urmărești un apel, scrie lângă fiecare parametru de tip referință numele variabilei din 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

De reținut