Transmiterea argumentelor prin valoare și prin referință
Transmiterea argumentelor prin valoare
Argumentele, care reprezintă variabile sau constante, pot fi transmise unei funcții prin valoare (by value) sau prin referință (by reference).
La transmiterea prin valoare, funcția primește o copie a valorilor variabilelor și constantelor. De exemplu:
#include <iostream>
void square(int); // prototipul funcției
int main()
{
int n {4};
std::cout << "Before square: n = " << n << std::endl;
square(n);
std::cout << "After square: n = " << n << std::endl;
}
void square(int m)
{
m = m * m;
std::cout << "In square: m = " << m << std::endl;
}
Funcția square primește un număr de tip int și îl ridică la pătrat. În funcția main, valoarea variabilei n este afișată înainte și după apelul funcției.
La execuție vom observa că modificarea parametrului m din funcția square nu afectează variabila n, ci doar copia ei:
Before square: n = 4
In square: m = 16
After square: n = 4
De ce se întâmplă asta? La compilare, pentru parametrii funcției se alocă locații separate de memorie. La apel, valorile argumentelor sunt evaluate și copiate în aceste locații. Funcția lucrează astfel cu copii ale valorilor, nu cu obiectele originale.
Transmiterea parametrilor prin referință
La transmiterea prin referință, se transmite o referință la obiect, astfel încât putem manipula chiar obiectul original. Să rescriem exemplul anterior folosind referință:
#include <iostream>
void square(int&); // prototipul funcției
int main()
{
int n {4};
std::cout << "Before square: n = " << n << std::endl;
square(n);
std::cout << "After square: n = " << n << std::endl;
}
void square(int& m)
{
m = m * m;
std::cout << "In square: m = " << m << std::endl;
}
Acum parametrul m este transmis prin referință. Parametrul referință este legat direct de obiectul transmis, deci modificarea lui afectează obiectul original n.
Ieșirea va fi:
Before square: n = 4
In square: m = 16
After square: n = 16
Transmiterea prin referință permite returnarea mai multor valori din funcție și este mai eficientă pentru obiecte mari, deoarece se evită copierea.
Transmiterea referințelor ca argumente
Transmiterea unei referințe ca argument e diferită de transmiterea prin referință. De exemplu:
#include <iostream>
void square(int); // prototipul funcției
int main()
{
int n = 4;
int &nRef = n; // referință la variabila n
std::cout << "Before square: n = " << n << std::endl;
square(nRef);
std::cout << "After square: n = " << n << std::endl;
}
void square(int m)
{
m = m * m;
std::cout << "In square: m = " << m << std::endl;
}
Dacă funcția primește argumente prin valoare, modificările nu afectează obiectele externe, chiar dacă transmiți referințe.
Ieșirea va fi:
Before square: n = 4
In square: m = 16
After square: n = 4
Transmiterea prin valoare e potrivită pentru obiecte mici, deoarece copierea valorilor e rapidă.
Transmiterea prin referință e recomandată pentru obiecte mari, fiind mai eficientă.
Conversia tipurilor
Există o altă diferență importantă: C++ poate converti automat valori dintr-un tip în altul (chiar dacă se pierde precizie, de exemplu double → int). Dar la transmiterea prin referință, conversiile implicite sunt excluse. De exemplu:
#include <iostream>
void printVal(int);
void printRef(int&);
int main()
{
double value{3.14159};
printVal(value); // 3
printRef(value); // ! Eroare
}
void printVal(int n)
{
std::cout << n << std::endl;
}
void printRef(int& n)
{
std::cout << n << std::endl;
}
Sunt definite două funcții asemănătoare. printVal primește prin valoare, printRef prin referință. La ambele se transmite un double, dar parametrii sunt de tip int. La transmiterea prin valoare, conversia are loc (cu pierdere de precizie). La referință, compilatorul va da eroare. Acesta e un alt motiv pentru a prefera referințele – previn conversiile neintenționate de tipuri.