Аргументы, которые представляют переменные или константы, могут передаваться в функцию по значению (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 (пусть и
с потерей точности), то при передаче по ссылке мы столкнемся с ошибкой на этапе компиляции. Это еще одна причина, почему нередко рекомендуется передавать значения по ссылки - исключается
вероятность предвиденных и иногда нежелательных преобразований типов.