Аргументы, которые представляют переменные или константы, могут передаваться в функцию по значению (by value) и по ссылке (by reference).
При передаче аргументов по значению функция получает копию значения переменных и констант. Например:
#include <iostream>
void square(int); // прототип функции
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;
}
Функция square принимает число типа int и возводит его в квадрат. В функции main перед и после выполнения функции square происходит вывод на консоль
значения переменной n, которая передается в square в качестве аргумента.
И при выполнении мы увидим, что изменение параметра m в функции square действуют только в рамках этой функции. Значение переменной n, которое передается в функцию, никак не изменяется:
Before square: n = 4 In square: m = 16 After square: n = 4
Почему так происходит? При компиляции функции для ее параметров выделяются отдельные участки памяти. При вызове функции вычисляются значения аргументов, которые передаются на место параметров. И затем значения аргументов заносятся в эти участки памяти. То есть функция манипулирует копиями значений объектов, а не самими объектами.
При передаче параметров по ссылке передается ссылка на объект, через которую мы можем манипулировать самим объектом, а не просто его значением. Так, перепишем предыдущий пример, используя передачу по ссылке:
#include <iostream>
void square(int&); // прототип функции
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;
}
Теперь параметр m передается по ссылке. Ссылочный параметр связывается непосредственно с объектом, поэтому через ссылку можно менять сам объект. То есть
здесь при вызове функции параметр m в функции square будет представлять тот же объект, что и переменная n
И если мы скомпилируем и запустим программу, то результат будет иным:
Before square: n = 4 In square: m = 16 After square: n = 16
Передача по ссылке позволяет возвратить из функции сразу несколько значений. Также передача параметров по ссылке является более эффективной при передаче очень больших объектов. Поскольку в этом случае не происходит копирования значений, а функция использует сам объект, а не его значение.
От передачи аргументов по ссылке следует отличать передачу ссылок в качестве аргументов:
#include <iostream>
void square(int); // прототип функции
int main()
{
int n = 4;
int &nRef = n; // ссылка на переменную 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;
}
Если функция принимает аргументы по значению, то изменение параметров внутри функции также никак не скажется на внешних объектах, даже если при вызове функции в нее передаются ссылки на объекты.
Before square: n = 4 In square: m = 16 After square: n = 4
Передача параметров по значению больше подходит для передачи в функцию небольших объектов, значения которых копируются в определенные участки памяти, которые потом использует функция.
Передача параметров по ссылке больше подходит для передачи в функцию больших объектов, в этом случае не нужно копировать все содержимое объекта в участок памяти, за счет чего увеличивается производительность программы.
Передача параметров по значению и по ссылке отличаются еще одним важным моментом. С++ может автоматически преобразовывать значения одних типов в другие, в том числе если подобные преобразования сопровождаются потерей точности (например, преобразование от типа double к типу int). Но при передаче параметров по ссылке неявные автоматические преобразования типов исключены. Так, рассмотрим пример:
#include <iostream>
void printVal(int);
void printRef(int&);
int main()
{
double value{3.14159};
printVal(value); // 3
printRef(value); // ! Ошибка
}
void printVal(int n)
{
std::cout << n << std::endl;
}
void printRef(int& n)
{
std::cout << n << std::endl;
}
Здесь определены две практически идентичные функции. Только функция printVal получает параметр по значению, а функция printRef - по ссылке. При вызове в обе функции передается
число типа double. Но параметр обоих функций представляет тип int. И если при передаче по значению переданное число double успешно преобразуется в int (пусть и
с потерей точности), то при передаче по ссылке мы столкнемся с ошибкой на этапе компиляции. Это еще одна причина, почему нередко рекомендуется передавать значения по ссылки - исключается
вероятность предвиденных и иногда нежелательных преобразований типов.