Памятка по for и range в Go #
Ключевое слово range используется вместе с for для перебора. Ниже памятка о разных вариантах использования for и for...range.
range по числовому диапазону #
Начиная с Go 1.22, можно выполнять range по целым числам:
n := 10
for i := range n {
// перебор i от 0 до n-1
}В любой версии Go работает for без range, что позволяет, например, перебрать числа в обратном порядке:
n := 10
for i := n-1; i >= 0; i-- {
// перебор от n-1 до 0
}range по map #
В языке Go map — это хеш-таблица:
- начиная с Go 1.24 используется открытая адресация и структура данных Swiss Table
- до версии Go 1.23 (включительно) использовалась закрытая адресация
Особенность перебора map в том, что начальная позиция в хеш-таблице при каждом переборе может быть выбрана случайно — то есть порядок перебора ключей может быть разным даже в соседних циклах for.
data := map[string]float64 {
"apple": 108.0,
"banana": 120.0,
"cherry": 500.0,
}
for key, value := range data {
// key перебирает ключи
// value перебирает соответствующие значения
}Начиная с Go 1.23, можно перебирать только ключи либо только значения функциями пакета maps:
data := map[string]float64 {
"apple": 108.0,
"banana": 120.0,
"cherry": 500.0,
}
for key := range maps.Keys(data) {
// key перебирает ключи
}
for value := range maps.Values(data) {
// value перебирает значения
}range по array / slice #
Перебор массивов и слайсов в Go работает одинаково:
// Перебор слайса строк.
data := []string{"apple", "banana", "cherry"}
for i, value := range data {
// i меняется от 0 до len(data)-1
// value меняется от "apple" до "cherry"
}Начиная с Go 1.23, можно перебрать слайс в обратном порядке с помощью slices.Backwards(s):\
// Перебор слайса строк в обратном порядке.
data := []string{"apple", "banana", "cherry"}
for i, value := range slices.Backward(data) {
// i меняется от len(data)-1 до 0
// value меняется от "cherry" до "apple"
}range по строке #
Строки в Go неизменяемые и хранятся в кодировке UTF-8, при этом:
len(text)возвращает число байт в строке, а число символов Unicode можно получить функциейutf8.RuneCountInString(text)text[i]предоставляет доступ к байту строки по индексуi
Однако перебор строки перебирает символы Unicode, а не байты:
text := "День космонавтики 🚀"
// перебор строки по символам Unicode
for i, c := range text {
// i меняется от 0 до utf8.RuneCountInString(text)-1
// c меняется от "Д" до "🚀"
fmt.Printf("%d -> %c\n", i, c)
}Если строку надо перебрать по байтам, можно сделать это с помощью преобразования []byte(text) либо обычным циклом for:
text := "🚀🇷🇺"
// перебор строки по байтам
for i, c := range []byte(text) {
// i меняется от 0 до len(text)-1
// c перебирает байты emoji-символа, закодированного в UTF-8
fmt.Printf("%d -> %d\n", i, c)
}
// работает аналогично предыдущему циклу
for i, iMax := 0, len(text); i < iMax; i++ {
fmt.Printf("%d -> %d\n", i, text[i])
}range по каналу #
Перебор канала означает чтение из него до тех пор, пока канал не будет закрыт отправителем. При этом даже после закрытия отправителем for дочитает значения до конца.
Помните:
- закрывать канал может только отправитель (sender), а не получатель (receiver);
- если в
for...rangeпо каналу передан nil, то goroutine зависнет (выполнение остановится навсегда)
package main
import (
"fmt"
"sync"
)
// Печатает: 1 2 3 4 5 6 7 8 9 10
func main() {
numbers := make(chan int, 3)
var wg sync.WaitGroup
wg.Add(2)
go func(numbers chan<- int) {
for i := range 10 {
numbers <- i + 1
}
close(numbers)
wg.Done()
}(numbers)
go func(numbers <-chan int) {
for num := range numbers {
fmt.Printf("%d ", num)
}
wg.Done()
}(numbers)
wg.Wait()
}range по функции #
В Go 1.23 появился for...range по функциям-итераторам:
// Перебирает квадраты целых чисел, начиная с 0.
squares := func(yield func(n int) bool) {
i := 0
for yield(i * i) {
i++
}
}
for x := range squares {
// Этот блок внутри for...range станет телом функции,
// которая будет передана как параметр `yield` в функцию `squares`.
if x > 100 {
// break приведёт к возврату false из функции, переданной в функцию `squares`.
break
}
fmt.Printf("%d ", x)
}Ссылки #
- Документация: https://go.dev/ref/spec#RangeClause
- Анонс range по функциям: https://go.dev/blog/range-functions