Обход среза: for и range
Срез собран — теперь по нему нужно пройти. Цикл со счётчиком из дня 08 подходит, а для срезов в Go есть форма короче — range.
Цикл по индексам
Ситуация. Напечатать опись: каждая ячейка с номером, как на табличке склада.
stock := []int{12, 40, 5}
for i := 0; i < len(stock); i++ {
fmt.Printf("Ячейка %d: %d шт.\n", i+1, stock[i])
}Три детали, каждая на своём месте:
i := 0— индексы начинаются с нуля;i < len(stock)— строго меньше: последний индексlen(stock)-1;i+1в печати — номер ячейки для человека, аstock[i]— элемент для программы.
range: тот же обход без счётчика
stock := []int{12, 40, 5}
for i, qty := range stock {
fmt.Printf("Ячейка %d: %d шт.\n", i+1, qty)
}Что увидите.
Ячейка 1: 12 шт.
Ячейка 2: 40 шт.
Ячейка 3: 5 шт.Как это читать. for i, qty := range stock — «для каждого элемента среза stock по порядку: его индекс положи в i, а сам элемент — в qty». Проходов ровно столько, сколько элементов; условия конца и i++ нет — range следит за границей сам.
qty — копия элемента, как параметр функции в дне 11. Если написать в теле qty = 0, срез не изменится. Чтобы изменить сам элемент, пишут по индексу: stock[i] = 0.
Эту форму вы уже набирали по образцу в дне 14: for _, c := range cases в табличном тесте — это обход среза случаев.
| Нужно | Пишите |
|---|---|
| и индекс, и значение | for i, qty := range stock |
| только значения | for _, qty := range stock — _ значит «индекс не нужен», как в дне 12 |
| только индексы | for i := range stock |
Если объявить обе переменные, а пользоваться одной, компилятор не соберёт программу — как с любой неиспользованной переменной:
stagiaire@lab:~/gocourse/day16/scratch$ go run .
# day16/scratch
./main.go:7:9: declared and not used: qtyПопробуйте сейчас: что выведет сумма через range.
Цель: предсказать одно число и сдать ответ.
1. Не запуская, прочитайте строки тела
main:Код для чтения · разбираем, набирать не нужноstock := []int{12, 40, 5, 40} sum := 0 for qty := range stock { sum += qty } fmt.Println(sum)2. Сдайте число:
▶ Выполните · выделенное замените своимcourse answer day16.q3 ЧИСЛО3. Проверьте запуском:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := []int{12, 40, 5, 40} sum := 0 for qty := range stock { sum += qty } fmt.Println(sum) }▶ Выполнитеcd ~/gocourse/day16/scratch go run .Готово, когда: пункт
q3вcourse check 16зелёный.
Разбор. С одной переменной range отдаёт индекс, а не значение — последняя строка таблицы выше. Имя qty ничего не меняет: компилятор не читает смысл имён. Цикл сложил индексы 0, 1, 2 и 3.
Выход за границу: паника
Ситуация. В цикле по индексам написали <= вместо <.
stock := []int{12, 40, 5}
for i := 0; i <= len(stock); i++ {
fmt.Println(stock[i])
}Что увидите.
stagiaire@lab:~/gocourse/day16/scratch$ go run .
12
40
5
panic: runtime error: index out of range [3] with length 3
goroutine 1 [running]:
main.main()
/home/stagiaire/gocourse/day16/scratch/main.go:8 +0x9b
exit status 2Как это читать. Программа собралась и честно напечатала три элемента. На четвёртом проходе i стал 3, а элемента с индексом 3 нет. Go не даёт прочитать память за границей среза: он останавливает программу — паникует. Сообщение читается по частям:
| Часть | Что значит |
|---|---|
panic: runtime error: |
программа остановлена во время работы, а не при сборке |
index out of range [3] |
какой индекс спросили |
with length 3 |
какая длина была у среза в этот момент |
main.main() и строка под ней |
в какой функции и на какой строке файла это случилось: main.go:8 |
exit status 2 |
это дописал go run: программа завершилась с кодом 2 |
Код 2 — знак паники: os.Exit(1) в дне 07 ставили вы сами, а код 2 ставит Go, когда программа сломалась. У собранного файла его показывает echo $? сразу после запуска.
Попробуйте сейчас: паника и код возврата.
Цель: увидеть панику в заготовке
fix2и её код возврата.1. Запустите заготовку описи:
▶ Выполнитеcd ~/gocourse/day16/fix2 go run . < example.txtЧто сейчас получается:
Вывод · это печатает программа, набирать не нужноЯчейка 1: 40 шт. Ячейка 2: 5 шт. panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.main() /home/stagiaire/gocourse/day16/fix2/main.go:29 +0x373 exit status 2Вместо
stagiaireу вас будет ваше имя пользователя.2. Соберите программу, запустите собранный файл и сразу спросите код возврата:
▶ Выполнитеgo build -o prog . ./prog < example.txt echo $?Готово, когда:
echo $?напечатал2, а строкиexit status 2у собранного файла нет — её печатает толькоgo run(пунктt_exit).
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
panic: runtime error: index out of range [3] with length 3 |
цикл дошёл до i == len(stock) |
условие i < len(stock) |
| числа в описи сдвинуты на одну ячейку: у первой — остаток второй | цикл начат с i := 1, а читает stock[i] |
индекс с 0, в печати номер i+1 |
| первой ячейки нет в выводе, паники нет | начали с i := 1 и поставили < |
либо i := 0 и печать i+1, либо читать stock[i-1] |
declared and not used: qty |
в range взяли значение и не пользуетесь им |
нужны только индексы — for i := range stock |
| сумма подозрительно маленькая: 0, 1, 3, 6 | в range одна переменная — это индекс |
for _, qty := range stock |
Попробуйте сейчас: починить fix2.
Цель: опись печатает все ячейки с номерами от 1 и не падает.
1. Откройте
~/gocourse/day16/fix2/main.goи найдите цикл описи — в сообщении паники указана его строка. Почините: одна-две строки, не больше.2. Проверьте и закоммитьте:
▶ Выполнитеcd ~/gocourse/day16/fix2 go run . < example.txt go run . < /dev/null diff ~/.course/orig/day16/fix2/main.go main.go cd ~/gocourse/day16 git add . git commit -m "fix2: индексы с нуля"Готово, когда: на
example.txtвыводятсяЯчейка 1: 12 шт.,Ячейка 2: 40 шт.,Ячейка 3: 5 шт.иЯчеек в описи: 3, без паники — пунктfix2.
Дальше: как записать цикл по срезу в таблицу трассировки — course next