- Писать функции, возвращающие несколько и именованные результаты
- Использовать вариативные функции и передавать функции как значения
- Объяснять, как замыкание захватывает внешнюю переменную
- Знать порядок выполнения
deferи момент вычисления его аргументов
Каждый раз, когда ты нажимаешь «+» на калькуляторе, срабатывает одно и то же правило сложения. В программировании такой многоразовый фрагмент кода называется функцией. Функции в Go умеют возвращать несколько результатов, принимать другие функции в качестве аргументов и даже «помнить» своё состояние. В этом уроке ты также познакомишься с defer — ключевым словом, которое откладывает действие до конца функции.
Объявление функций и результаты
Функция объявляется ключевым словом func: сначала имя, потом параметры в скобках и в конце тип результата. Параметры одного типа можно сократить: a, b int вместо a int, b int. Отличительная черта Go — функция может возвращать несколько значений: (int, int). Ненужный результат можно отбросить с помощью пустого идентификатора _.
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func rectangle(w, h float64) (area, perimeter float64) {
area = w * h
perimeter = 2 * (w + h)
return
}
func main() {
fmt.Println(add(2, 3))
q, r := divmod(17, 5)
fmt.Println(q, r)
_, rest := divmod(10, 4)
fmt.Println(rest)
fmt.Println(rectangle(3, 2.5))
}5 3 2 2 7.5 11
В функции rectangle у результатов есть имена: (area, perimeter float64). Такие переменные создаются с нулевыми значениями при входе в функцию, а return без аргументов возвращает их текущие значения. Как видно в последней строке, вызов функции с несколькими результатами можно даже сразу передать в Println.
Вариативные функции и функции как значения
Если перед типом последнего параметра поставить ..., функция будет принимать любое число аргументов — такая функция называется вариативной. Именно такая функция — fmt.Println. Внутри функции этот параметр — обычный срез. Чтобы передать готовый срез, после его имени пишут ...: sum(scores...). Кроме того, функции в Go — обычные значения: их можно сохранять в переменные, передавать в другие функции и возвращать.
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func apply(n int, f func(int) int) int {
return f(n)
}
func main() {
fmt.Println(sum())
fmt.Println(sum(1, 2, 3))
scores := []int{90, 85, 70}
fmt.Println(sum(scores...))
square := func(x int) int { return x * x }
fmt.Println(apply(7, square))
}0 6 245 49
Замыкания
Анонимная функция создаётся прямо внутри кода: func(x int) int { ... }. Если она использует внешнюю переменную, то «захватывает» её и продлевает ей жизнь — такая функция называется замыканием (closure). Функция counter ниже при каждом вызове создаёт новый счётчик: у каждого замыкания своя переменная count.
package main
import "fmt"
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next())
other := counter()
fmt.Println(other(), next())
}1 2 3 1 4
next и other — два независимых счётчика: other() снова начинает с 1, а next() продолжает свой счёт и возвращает 4. Переменная count продолжает жить после выхода из counter, потому что её удерживает замыкание.
var funcs []func()
for i := 0; i < 3; i++ {
i := i // needed before Go 1.22
funcs = append(funcs, func() { fmt.Print(i, " ") })
}var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() { fmt.Print(i, " ") })
}defer: отложить на потом
Ключевое слово defer откладывает вызов функции до завершения текущей функции. Завершится ли функция нормально или выйдет с ошибкой — отложенные вызовы выполнятся обязательно. Если defer несколько, они выполняются в обратном порядке — от последнего к первому (LIFO, как стопка тарелок). Внимание: аргументы вычисляются в момент записи defer, а не потом.
package main
import "fmt"
func main() {
fmt.Println("start")
for i := 1; i <= 3; i++ {
defer fmt.Println("deferred", i)
}
x := 10
defer fmt.Println("x was", x)
x = 20
fmt.Println("end, x =", x)
}start end, x = 20 x was 10 deferred 3 deferred 2 deferred 1
Посмотри на результат: хотя x потом стал равен 20, отложенный вызов напечатал 10, потому что аргумент был вычислен в строке с defer. Все отложенные вызовы сработали после строки end, в обратном порядке.
Главное
- Функция записывается как
func имя(параметры) результат; параметры одного типа можно объединить:a, b int. - Функция в Go может возвращать несколько значений; ненужный результат отбрасывается через
_. - Параметр
...Tпринимает любое число аргументов; готовый срез передаётся какs.... - Замыкание захватывает и хранит внешнюю переменную; начиная с Go 1.22 у каждой итерации цикла своя переменная.
deferоткладывает вызов до конца функции, выполняет отложенные вызовы в обратном порядке и сразу вычисляет аргументы.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
func divmod(a, b int) (int, int) правильный?