Параметры и аргументы
Параметры функции - это именованные переменные в её определении; аргументы - это фактические значения, которые вы ей передаёте при вызове. На предыдущей странице было показано, как определять и вызывать функции; эта страница о том, как эти значения на самом деле попадают внутрь, потому что C++ предлагает несколько способов, и выбор влияет как на корректность, так и на скорость.
По умолчанию в C++ используется передача по значению: функция получает копию.
Внутри addTen n - это отдельная переменная, инициализированная из score. Переприсваивание n затрагивает только эту копию, поэтому score остаётся нетронутым после возврата в main. Это безопасно и предсказуемо - функция не может случайно затереть ваши данные - именно поэтому такой способ принят по умолчанию.
Передача по ссылке: позволяем функции изменять вызывающую сторону
Иногда вы хотите, чтобы функция изменила переменную вызывающей стороны. Добавьте & к типу параметра, и он станет ссылкой - псевдонимом оригинала, а не копией:
Единственное отличие от первого примера - это &, но теперь n и score - это один и тот же объект. Это стандартный способ «вернуть» более одного значения или обновить что-то на месте. Классическое применение - обмен двух переменных:
Без & swapValues перемешала бы две копии, а main вообще не увидела бы изменений - очень распространённая ошибка новичков.
Const-ссылки: дешёвый доступ только для чтения
Передача по значению копирует аргумент. Для int это ничего не стоит, но копировать большой string или vector при каждом вызове - это реальная, напрасно потраченная работа. Решение - const-ссылка (const T&): вы получаете скорость ссылки (без копирования) плюс гарантированное компилятором обещание не изменять аргумент.
Удобное эмпирическое правило: передавайте небольшие встроенные типы (int, double, char, bool, указатели) по значению, а большие объекты, которые нужно только читать, - по const-ссылке. Оставьте обычную неконстантную T& для случаев, когда вы действительно намерены изменить объект вызывающей стороны.
Тонкий нюанс: обычная int& n не может связаться с временным объектом или литералом. addTen(5) из первого примера не скомпилировался бы, будь параметр int&, потому что 5 - это не переменная, для которой можно создать псевдоним. А const int& может связаться с 5, что является ещё одной причиной столь широкого использования const-ссылок.
Аргументы по умолчанию
Вы можете задать параметру запасное значение, чтобы вызывающие стороны могли его опустить. Если аргумент отсутствует, используется значение по умолчанию:
Два правила сбивают людей с толку. Во-первых, значения по умолчанию должны быть в конце - как только у параметра есть значение по умолчанию, оно должно быть и у каждого параметра после него. Нельзя написать void f(int a = 1, int b), потому что не было бы способа задать b, пропустив a. Во-вторых, когда функция объявлена в заголовке и определена в другом месте, указывайте значение по умолчанию только в объявлении, никогда не повторяйте его в определении - повторение является ошибкой компиляции.
Передача массивов и векторов
Сырой массив при передаче распадается в указатель, поэтому функция теряет информацию о его размере - вы почти всегда передаёте длину рядом с ним:
Поскольку массив стал указателем, sizeof(arr) внутри sum дал бы размер указателя, а не массива - печально известная ошибка. В современном C++ предпочитайте std::vector (или std::span в C++20), передаваемый по const-ссылке, который несёт собственный размер:
Обратите внимание на const&: уберите его, и каждый вызов копирует весь вектор. Для вектора из четырёх элементов это безобидно, но для миллиона элементов это незаметная утечка производительности.
Параметры-указатели
Вы также можете передать указатель (T*). Как и ссылка, это позволяет функции добраться до данных вызывающей стороны, но указатель можно переустановить или он может быть нулевым - поэтому это правильный инструмент, когда «нет значения» является допустимым вариантом:
Вызывающая сторона передаёт &value, чтобы поделиться его адресом, а функция записывает через *out. Ключевое отличие от ссылок: указатель может быть nullptr, поэтому функция, принимающая его, должна проверить перед разыменованием - пропуск этой проверки и разыменование нулевого указателя - это неопределённое поведение, обычно аварийное завершение. Если «нет значения» никогда не имеет смысла, ссылка чище, потому что она в принципе не может быть нулевой.
Далее: Ссылки
Параметры - это место, где ссылки отрабатывают своё назначение, но ссылки - это самостоятельная возможность: псевдонимы, которые вы можете создать для любой переменной, а не только внутри сигнатуры функции. Следующая страница подробно разбирает, как ссылки работают сами по себе: как их объявлять, почему они должны инициализироваться немедленно, разницу между lvalue-ссылкой и const-ссылкой и те тонкие способы, которыми ссылка может оказаться висячей (dangling).
Часто задаваемые вопросы
В чём разница между передачей по значению и передачей по ссылке в C++?
Передача по значению копирует аргумент в параметр, поэтому изменения внутри функции не затрагивают вызывающую сторону. Передача по ссылке (int&) делает параметр псевдонимом переменной вызывающей стороны, поэтому изменения видны снаружи. Используйте void f(int x), чтобы копировать, и void f(int& x), чтобы изменить оригинал.
Когда следует использовать параметр-const-ссылку в C++?
Используйте const T&, когда хотите прочитать большой объект, не копируя его и не позволяя функции его изменять, - например, void print(const string& s). Это даёт скорость передачи по ссылке вместе с безопасностью передачи по значению. Для маленьких типов вроде int или char обычная передача по значению столь же быстра.
Что такое аргументы по умолчанию в C++?
Аргументы по умолчанию позволяют параметру принимать запасное значение, когда вызывающая сторона его опускает, например void greet(string name = "there"). Значения по умолчанию должны быть у завершающих (самых правых) параметров, и указывать их следует только в объявлении, а не в определении, если они разделены.