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

Своя функция: имя для куска работы

С дня 04 вы вызываете чужие функции: strings.ToUpper, strconv.Atoi, math.Round, fmt.Sprintf. Кто-то их написал, вам достался готовый результат. Сегодня вы пишете такую же — свою.

Одно важное отличие от всего, что было до сих пор: функцию нельзя написать внутри main. Она объявляется рядом с ним, на верхнем уровне файла. Поэтому программы сегодня показаны целиком — и набирают их в scratch/main.go целиком, вместо того что там лежит. Когда дальше в разборе меняются одна-две строки, показаны только они, и прямо сказано, какая это строка программы.


Одно и то же дважды

Ситуация. На двух складах лежит товар, коробка вмещает 6 штук. Нужно сказать, сколько коробок уйдёт на каждый склад. Неполная коробка — тоже коробка.

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

import "fmt"

func main() {
	qtyA := 14
	boxesA := qtyA / 6
	if qtyA%6 != 0 {
		boxesA++
	}
	fmt.Println("Склад A:", boxesA, "кор.")

	qtyB := 30
	boxesB := qtyB / 6
	if qtyB%6 != 0 {
		boxesB++
	}
	fmt.Println("Склад B:", boxesB, "кор.")
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Склад A: 3 кор.
Склад B: 5 кор.

Как это читать. Программа работает и считает правильно. Плохо в ней другое: четыре строки расчёта написаны дважды, слово в слово, отличаются только имена переменных. Появится склад C — они появятся в третий раз.

Это не вопрос красоты. У повтора есть цена, и она измеримая:

Функция решает всё это сразу: расчёт лежит в одном месте, и у него есть имя.


Как объявляется функция

Та же задача, тот же вывод:

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

import "fmt"

func boxes(qty int) int {
	n := qty / 6
	if qty%6 != 0 {
		n++
	}
	return n
}

func main() {
	fmt.Println("Склад A:", boxes(14), "кор.")
	fmt.Println("Склад B:", boxes(30), "кор.")
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Склад A: 3 кор.
Склад B: 5 кор.

Как это читать. Первая строка объявления — заголовок функции. В нём четыре части:

Кусок Как называется Что значит
func ключевое слово «дальше объявляется функция»
boxes имя по нему функцию вызывают
(qty int) параметры что функция получает снаружи и под каким именем это видно внутри
int после скобок тип результата что функция отдаёт обратно
{ … } тело что она делает

main — тоже функция, вы объявляете её с дня 01. У неё нет ни параметров, ни результата, поэтому после main() ничего не написано: func main() { — не заклинание, а обычный заголовок функции с пустыми скобками. И тело функции живёт по тем же правилам, что тело main: переменные, if, циклы, вызовы других функций. Новое здесь одно — return.

return n делает две вещи сразу:

  1. заканчивает работу функции — строки после него не выполняются;
  2. отдаёт значение n туда, откуда функцию вызвали.

Вызов boxes(14) — это выражение со значением, такое же, как 2 + 2 или qtyA / 6. Его можно напечатать, положить в переменную, сложить с другим числом, сравнить:

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

import "fmt"

func boxes(qty int) int {
	n := qty / 6
	if qty%6 != 0 {
		n++
	}
	return n
}

func main() {
	a := boxes(14)
	b := boxes(30)
	fmt.Println("Всего коробок:", a+b)
	fmt.Println("На складе A больше:", a > b)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Всего коробок: 8
На складе A больше: false

Строка a := boxes(14) читается справа налево, как любое присваивание из дня 02: сначала вычисляется правая часть — то есть работает функция, — и её результат кладётся в ячейку a.


Попробуйте сейчас: две программы рядом.

Цель: обе программы — с повтором и с функцией — набраны своими руками и печатают одно и то же.

1. Сотрите заготовку в scratch/main.go и наберите программу из раздела «Одно и то же дважды» — вот она целиком:

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

import "fmt"

func main() {
	qtyA := 14
	boxesA := qtyA / 6
	if qtyA%6 != 0 {
		boxesA++
	}
	fmt.Println("Склад A:", boxesA, "кор.")

	qtyB := 30
	boxesB := qtyB / 6
	if qtyB%6 != 0 {
		boxesB++
	}
	fmt.Println("Склад B:", boxesB, "кор.")
}

Запустите:

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

2. Теперь наберите вместо неё программу из раздела «Как объявляется функция» и запустите снова. Вывод должен быть тот же.

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

import "fmt"

func boxes(qty int) int {
	n := qty / 6
	if qty%6 != 0 {
		n++
	}
	return n
}

func main() {
	fmt.Println("Склад A:", boxes(14), "кор.")
	fmt.Println("Склад B:", boxes(30), "кор.")
}
▶ Выполните
go run .

3. Допишите третий склад: в первой программе — ещё четыре строки расчёта, во второй — один вызов boxes(7). Сравните, сколько пришлось набрать в том и в другом случае.

Готово, когда: обе программы печатают Склад A: 3 кор. и Склад B: 5 кор., а со складом C во второй программе добавилась ровно одна строка.


Функция объявлена — вызывать её не обязательно

Переменную, которую объявили и не использовали, компилятор не пропускает (declared and not used — день 02). С функциями правило другое: объявить можно и не вызывать. Программа, в которой boxes объявлена, а main печатает только готово, собирается и работает — ни слова упрёка.

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


Попробуйте сейчас: своя функция с нуля.

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

Вес посылки складывается из веса товара и веса упаковки: упаковка — 120 г.

1. В scratch/main.go объявите функцию с заголовком func parcel(goods int) int, которая возвращает вес посылки в граммах. Тело — одна строка с return.

2. В main напечатайте вес посылки для товара 800 г и для товара 1500 г — двумя вызовами:

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

Готово, когда: программа печатает два числа — 920 и 1620, — а слово 120 встречается в файле ровно один раз.


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

Функция обещала результат, но не вернула его:

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

import "fmt"

func boxes(qty int) int {
	n := qty / 6
	if qty%6 != 0 {
		n++
	}
}

func main() {
	fmt.Println("Склад A:", boxes(14), "кор.")
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:10:1: missing return

Строка 10 — закрывающая скобка функции. Компилятор говорит: «дошли до конца тела, а return так и не встретился». Тот же случай, когда return спрятан внутри if, — в программе изменена только функция:

Код для чтения · разбираем, набирать не нужно
func boxes(qty int) int {
	if qty > 0 {
		return 1
	}
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:9:1: missing return

Условие может оказаться ложным — и тогда возвращать нечего. Компилятор проверяет не «бывает ли так на самом деле», а «может ли выполнение дойти до конца тела». Лечение — return после if или else с return.

Обратная ошибка: функция ничего не обещала, а её результат печатают:

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

import "fmt"

func report() {
	fmt.Println("отчёт готов")
}

func main() {
	fmt.Println(report())
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:10:14: report() (no value) used as value

report печатает сама и ничего не отдаёт — значит, её вызов не выражение и внутри Println ему не место.

И самый тихий случай: функция вернула значение, а его никто не взял.

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

import "fmt"

func boxes(qty int) int {
	return qty / 6
}

func main() {
	boxes(14)
	fmt.Println("готово")
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/scratch$ go run .
готово

Ни ошибки, ни предупреждения — go vet тоже молчит. Строка boxes(14) законна: функция отработала, вернула 2, и это значение выбросили. С len(s) и другими встроенными функциями так нельзя, а с обычными — можно, поэтому забытое присваивание придётся замечать самому. На второй странице вы почините программу именно с этой ошибкой.

Что видите Что это значит Что делать
./main.go:10:1: missing return в заголовке обещан тип результата, а до конца тела return может не встретиться return в конце тела или в каждой ветке if/else
./main.go:10:14: report() (no value) used as value результат берут у функции, которая его не возвращает либо дописать тип результата и return, либо не использовать вызов как значение
./main.go:6:7: syntax error: unexpected name boxes, expected ( объявление функции написано внутри main объявлять рядом с main, на верхнем уровне файла
программа собирается, но считает не то; вызов стоит отдельной строкой результат вернулся и был выброшен присвоить результат или напечатать его
undefined: boxes функция объявлена в другом файле, не в этом пакете, или в имени опечатка сверить имя буква в букву — пятая страница

Дальше: параметры, аргументы и главное свойство передачи — course next