Перейти к содержимому
Теория · Функции · урок 1 из 10

Параметры — это копии

Чему вы научитесь

Чему вы научитесь:

  • объяснять, почему функция меняет только копию своего параметра, и возвращать новое значение через return;
  • возвращать из функции несколько результатов и отбрасывать лишние через _;
  • писать рекурсивные функции с базовым случаем;
  • понимать, где видна переменная и что такое затенение.
Копировальный станок double снимает копию листа x = 5 и выдаёт лист n = 10, а лист x = 5 остаётся на стекле

Функцию удобно представлять станком с копировальным аппаратом внутри. Вы кладёте на стекло лист с числом, станок что-то с этим числом делает и выдаёт готовый лист:

  • то, что вы кладёте на стекло, — параметры функции;
  • лист на выходе — результат, который она возвращает через return.

У этого станка есть особенность: он никогда не трогает ваш лист. Перед работой он снимает с него копию и работает с копией, а оригинал так и лежит на стекле.

Посмотрим, как это выглядит в коде. Функция double удваивает n, а переменная x в main остаётся прежней:

GO
package main

import "fmt"

func double(n int) {
    n = n * 2
    fmt.Println("inside:", n) // inside: 10
}

func main() {
    x := 5
    double(x)
    fmt.Println("outside:", x) // outside: 5
}

Передача по значению

Давайте разберём пример по строкам:

  1. x := 5 создаёт переменную x в main.
  2. При вызове double(x) Go копирует значение аргумента x в параметр n.
  3. n = n * 2 превращает копию в 10.
  4. Первый Println печатает inside: 10.
  5. Функция завершается, и второй Println печатает outside: 5.

В этом разборе встретились два слова, которые часто путают:

  • параметр — имя в объявлении функции: n в func double(n int);
  • аргумент — значение, которое передают при вызове: x в double(x).

Такой способ передачи называют передачей по значению. Функция получает копию аргумента, а не саму переменную.

Станок снимает копию при каждом запуске, и Go тоже копирует аргумент при каждом вызове. Для числа это быстрая операция, и вы её даже не замечаете.

Этот вопрос часто задают на собеседовании: «Как Go передаёт аргументы — по значению или по ссылке?» Ответ собирается из двух частей:

  • всегда по значению: Go копирует любой аргумент, а не только числа;
  • присваивание параметру никогда не меняет переменную вызывающего кода.

Параметр — отдельная переменная

Параметр — обычная переменная функции:

  • её можно читать, менять через = и ++, передавать в другие вызовы;
  • она создаётся при каждом вызове и исчезает, когда функция завершается;
  • с переменной вызывающего кода она никак не связана.

Есть два случая, когда связь только кажется.

Выражение в аргументе

Аргументом может быть выражение, а не только переменная. При вызове double(x + 1) Go сначала вычисляет сумму 6 и копирует её значение в n. Функция видит только число и не знает, из чего его получили.

Одинаковые имена

Одинаковые имена тоже ничего не связывают. Если назвать параметр x, как переменную в main, переменных всё равно будет две. Каждая видна только в своей функции, и изменение одной не трогает другую.

Вернуть новое значение

Функция связана с вызывающим кодом только двумя путями:

  • аргументы на входе;
  • результат на выходе.

Чтобы вызывающий код получил изменённое значение, функция возвращает его через return. Вызывающий сам решает, что сделать с результатом: напечатать, записать в новую переменную или обратно в x.

GO
package main

import "fmt"

func doubled(x int) int {
    x = x * 2
    return x
}

func main() {
    x := 5
    fmt.Println(x, doubled(x)) // 5 10
    x = doubled(x)
    fmt.Println(x) // 10
}

Разберём main по строкам:

  1. x := 5 создаёт переменную x в main.
  2. fmt.Println(x, doubled(x)) вызывает doubled: функция меняет свой параметр x и возвращает 10, а x в main остаётся 5. Строка печатает 5 10.
  3. x = doubled(x) записывает результат второго вызова обратно в x.
  4. fmt.Println(x) печатает 10: переменная изменилась только после присваивания.

Станок выдал новый лист, а положили его на место старого вы сами.

Частая ошибка

Функция возвращает новое значение, но результат вызова никуда не записали:

GO
x := 5
doubled(x) // ✗
fmt.Println(x)

Программа компилируется и печатает 5. Функцию можно вызвать отдельной инструкцией, даже если она возвращает значение, и тогда результат пропадает. Блюдо приготовлено, но его никто не взял. Компилятор об этом не предупреждает, поэтому результат присваивайте явно: x = doubled(x).

Главное

  • Параметр — копия аргумента: изменения внутри функции не видны снаружи.
  • Go всегда передаёт аргументы по значению.
  • Чтобы вызывающий код получил новое значение, функция возвращает его, а вызывающий присваивает: x = doubled(x).

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом