День 11 · Своя функция: объявление, параметры, возврат · страница 2 из 7

Параметры и аргументы: внутрь уходит копия

Функция без параметров считала бы одно и то же число. Всю пользу даёт то, что снаружи ей передают данные: boxes(14) и boxes(30) — одна и та же функция и два разных ответа. Сегодняшняя страница — про то, как данные попадают внутрь и что с ними там происходит.


Два имени для одного и того же

Ситуация. Цена за штуку перестала быть постоянной: у разных товаров она разная. Значит, функции нужно передавать два числа.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func cost(qty int, price int) int {
	return qty * price
}

func main() {
	fmt.Println("Заказ 3 шт.:", cost(3, 4500), "коп.")
	fmt.Println("Заказ 12 шт.:", cost(12, 4500), "коп.")
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Заказ 3 шт.: 13500 коп.
Заказ 12 шт.: 54000 коп.

Как это читать. У одного и того же числа здесь два имени, и важно их не путать:

Слово Где живёт Пример выше
параметр в заголовке функции; это имя, под которым значение видно внутри qty, price
аргумент в вызове; это значение, которое передают снаружи 3 и 4500

Параметры перечисляются через запятую, у каждого свой тип. Подряд идущие одинаковые типы можно не повторять — func cost(qty, price int) int значит ровно то же самое, — но в курсе пишем полную форму.

Аргументы попадают в параметры строго по порядку. Первый аргумент — в первый параметр, второй — во второй; имена переменных в main на это не влияют. Можно передать cost(n, p), cost(3, price) или cost(qty*2, 4500) — считается только позиция. Это источник тихой ошибки, о которой будет пятая страница.

Параметр — обычная переменная функции, просто созданная не :=, а самим вызовом: её видно в любом месте тела, ей можно присваивать.


Сколько аргументов и какого типа

Компилятор сверяет вызов с заголовком по числу аргументов и по типам. Дальше — та же программа, в которой меняется только main. Аргументов меньше, чем параметров: в main остался один вызов, строка 10, и в нём пропал второй аргумент.

Код для чтения · разбираем, набирать не нужно
	fmt.Println(cost(3))
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:10:19: not enough arguments in call to cost
	have (number)
	want (int, int)

Три строки: что не так, что передали (have) и что нужно (want). number вместо int — потому что 3 в исходнике ещё не число определённого типа, а просто число, как константы из дня 03.

Аргументов больше — та же программа, изменена только строка 10:

Код для чтения · разбираем, набирать не нужно
	fmt.Println(cost(3, 4500, 2))
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:10:28: too many arguments in call to cost
	have (number, number, number)
	want (int, int)

Тип не тот — в main добавлена строка со строкой в кавычках:

Код для чтения · разбираем, набирать не нужно
	size := "3"
	fmt.Println(cost(size, 4500))
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:11:19: cannot use size (variable of type string) as int value in argument to cost

Сообщение длинное, но читается по частям: «нельзя использовать size (переменная типа string) как значение int в аргументе функции cost». Строка, в которой лежит "3", числом не станет сама — нужен strconv.Atoi из дня 07.

Ещё одно правило, о котором стоит знать заранее: параметр, который функция не использует, — не ошибка.

Код для чтения · разбираем, набирать не нужно
func label(qty int, note string) string {
	return "коробка"
}

Вызов fmt.Println(label(3, "срочно")) собирается и печатает коробка: ни qty, ни note в теле не читаются, и компилятор молчит. declared and not used для параметров не бывает никогда — ни для одного, ни для всех сразу. Правило «объявил — используй» касается только переменных, созданных внутри тела: строка rest := qty % 10, если rest нигде не читают, даст знакомое с дня 02 declared and not used: rest.


Попробуйте сейчас: три ошибки вызова.

Цель: увидеть своими глазами все три сообщения о неверном вызове и записать их в словарик.

1. Наберите в scratch/main.go программу с функцией cost из раздела «Два имени для одного и того же» — вот она целиком:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func cost(qty int, price int) int {
	return qty * price
}

func main() {
	fmt.Println("Заказ 3 шт.:", cost(3, 4500), "коп.")
	fmt.Println("Заказ 12 шт.:", cost(12, 4500), "коп.")
}

Запустите — она должна работать:

▶ Выполните
cd ~/gocourse/day11/scratch
go run .

2. Теперь по очереди сломайте вызов и каждый раз запускайте go run .: уберите второй аргумент; верните и добавьте третий; верните и передайте первым аргументом строку в кавычках. Каждое сообщение — строкой в ~/errors.md: текст ошибки, тире, что значило.

Готово, когда: вы получили все три сообщения и в ~/errors.md появились строки про not enough arguments, too many arguments и cannot use … as int value in argument — они идут в пункт errors.


Внутрь уходит копия

Ситуация. Кажется естественным: раз параметр — это переменная, то, меняя её в функции, мы меняем то, что передали. Проверим.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func addOne(n int) {
	n = n + 1
	fmt.Println("внутри функции n =", n)
}

func main() {
	qty := 5
	addOne(qty)
	fmt.Println("в main qty =", qty)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
внутри функции n = 6
в main qty = 5

Как это читать. В момент вызова Go берёт значение аргумента и кладёт его копию в новую ячейку — параметр n. Дальше это два разных места в памяти: qty в main и n в addOne. Присваивание n = n + 1 меняет только вторую ячейку, а когда функция заканчивается, её ячейки перестают существовать — от изменения не остаётся ничего. Это ровно то же копирование, что при обычном присваивании в дне 02: b := a копирует значение, и потом b = b + 1 не трогает a.

Правило коротко: менять параметр внутри функции бесполезно, если результат не возвращён. Единственный способ отдать что-то наружу — return, и единственный способ этим воспользоваться — присвоить возвращённое значение или сразу его использовать. В той же программе у функции появился тип результата и return, а вызов в main стал присваиванием — qty = addOne(qty):

Код для чтения · разбираем, набирать не нужно
func addOne(n int) int {
	n = n + 1
	return n
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
в main qty = 6

В строке qty = addOne(qty) справа qty читается — её значение копируется в параметр, — а слева в ту же qty записывается результат. Порядок тот же, что у x = x + 1 из дня 02.

Про срезы, map и указатели, где эта картина сложнее, речь пойдёт в блоках 4 и 5. Пока параметры — числа, строки и bool, и правило работает без исключений.


Попробуйте сейчас: что напечатает программа.

Цель: предсказать вывод программы, где функция меняет параметр, и сдать ответ.

1. Сначала не запуская — что напечатает эта программа?

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func discount(price int) int {
	price = price - 500
	return price
}

func main() {
	price := 4500
	discount(price)
	fmt.Println("Цена:", price)
}

2. Сдайте число, которое напечатается после слова Цена::

▶ Выполните · выделенное замените своим
course answer day11.q1 ЧИСЛО

Вместо ЧИСЛО — ваше предсказание.

3. Проверьте запуском — наберите эту программу в scratch/main.go:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func discount(price int) int {
	price = price - 500
	return price
}

func main() {
	price := 4500
	discount(price)
	fmt.Println("Цена:", price)
}
▶ Выполните
cd ~/gocourse/day11/scratch
go run .

Готово, когда: пункт q1 в course check 11 зелёный.


Попробуйте сейчас: два вызова, одно присваивание.

Цель: предсказать вывод программы, где один вызов присвоен, а другой нет.

1. Не запуская — что напечатает эта программа? Разберите отдельно строку weight = addPack(weight) и отдельно addPack(weight).

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func addPack(weight int) int {
	weight = weight + 200
	return weight
}

func main() {
	weight := 1000
	weight = addPack(weight)
	addPack(weight)
	fmt.Println("Вес:", weight)
}

2. Сдайте число, которое напечатается после слова Вес::

▶ Выполните · выделенное замените своим
course answer day11.q2 ЧИСЛО

3. Проверьте запуском — наберите эту программу в scratch/main.go:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func addPack(weight int) int {
	weight = weight + 200
	return weight
}

func main() {
	weight := 1000
	weight = addPack(weight)
	addPack(weight)
	fmt.Println("Вес:", weight)
}
▶ Выполните
cd ~/gocourse/day11/scratch
go run .

Готово, когда: пункт q2 в course check 11 зелёный.


Попробуйте сейчас: починить fix1.

Цель: fix1 печатает вес партии вместе с упаковкой; правка — одна-две строки, больше двух проверка не примет.

1. Запустите как есть и прочитайте комментарий в начале файла:

▶ Выполните
cd ~/gocourse/day11/fix1
go run .

2. Функция addPack считает верно — убедитесь в этом, прочитав её. Значит, дело в том, что происходит с её результатом. Числа в main не меняйте, правьте одну-две строки. После правки запустите снова и сравните с оригиналом:

▶ Выполните
go run .
diff ~/.course/orig/day11/fix1/main.go main.go

Готово, когда: напечатанный вес больше исходного веса товара ровно на вес упаковки всех коробок, а diff показывает одну-две изменённые строки — пункты fix1 и fixsmall.


Что может пойти не так

Что видите Что это значит Что делать
not enough arguments in call to cost и две строки have/want аргументов меньше, чем параметров сверить вызов с заголовком: сколько параметров, столько аргументов
too many arguments in call to cost аргументов больше лишний аргумент убрать
cannot use size (variable of type string) as int value in argument to cost тип аргумента не совпал с типом параметра преобразовать: strconv.Atoi для строки, float64(x) для числа
declared and not used: rest внутри функции переменная объявлена в теле и не читается использовать или удалить; на параметры это правило не распространяется
функция меняет параметр, а снаружи ничего не меняется внутрь ушла копия вернуть значение через return и присвоить его при вызове
вызов стоит отдельной строкой, программа печатает старое значение результат вернулся и был выброшен x = f(x) вместо f(x)
cannot use qty (variable of type int) as string value in argument to label перепутаны местами аргументы разных типов аргументы сопоставляются по порядку, а не по имени

Дальше: функция, которая ничего не возвращает, и выход из неё досрочно — course next