День 16 · Срезы: много значений в одной переменной · страница 3 из 7

Обход среза: 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])
	}

Три детали, каждая на своём месте:


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.go
package 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