День 08 · Циклы: for, границы, накопители · страница 3 из 7

Цикл с условием, break и continue

Число проходов не всегда известно заранее: «вывозить, пока на складе что-то есть». Для таких задач у for есть ещё две формы.


for с одним условием

Ситуация. На складе 50 штук, машина забирает 12. Сколько нужно рейсов?

Код для чтения · разбираем, набирать не нужно
	stock := 50
	trips := 0
	for stock > 0 {
		stock -= 12
		trips++
	}
	fmt.Println("Рейсов:", trips)
	fmt.Println("Остаток после вывоза:", stock)

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
Рейсов: 5
Остаток после вывоза: -10

Как это читать. В заголовке только условие, и проверяется оно как вторая часть обычного for: истинно — тело, ложно — выход. Своей переменной у такого цикла нет: stock и trips объявлены до него и после живы. В других языках это while; в Go отдельного слова нет.

Остаток −10 значит, что пятая машина ушла недогруженной на 10 штук: тело выполняется целиком, а условие проверяется только перед следующим проходом. Было бы на складе 0 с начала — рейсов было бы 0. Счётчик в такой форме пишут руками: i := 0 до цикла, i++ последней строкой тела.


Попробуйте сейчас: рейсы при других остатках.

Цель: посчитать рейсы и остаток при stock := 60 и stock := 0 заранее, потом проверить.

1. Наберите программу про рейсы в scratch/main.go:

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

import "fmt"

func main() {
	stock := 50
	trips := 0
	for stock > 0 {
		stock -= 12
		trips++
	}
	fmt.Println("Рейсов:", trips)
	fmt.Println("Остаток после вывоза:", stock)
}

2. Посчитайте на бумаге, что будет при stock := 60 и при stock := 0, и проверьте — по разу с каждым значением:

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

Готово, когда: оба запуска совпали с посчитанным.


Бесконечный цикл и Ctrl+C

Ситуация. В программе про рейсы забыли строку stock -= 12.

Код для чтения · разбираем, набирать не нужно
	stock := 50
	trips := 0
	for stock > 0 {
		trips++
		fmt.Println("Рейс", trips, "остаток", stock)
	}
	fmt.Println("Рейсов:", trips)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
Рейс 1 остаток 50
Рейс 2 остаток 50
Рейс 3 остаток 50
Рейс 4 остаток 50
Рейс 5 остаток 50
Рейс 6 остаток 50
Рейс 132025 остаток 50
Рейс 132026 остаток 50
Рейс 132027 остаток 50
Рейс 132028 остато^C
signal: interrupt

Здесь пропущено больше ста тысяч строк — столько успело напечататься за секунды (номер последнего рейса у вас будет свой). Условие stock > 0 зависит только от stock, а тело его не меняет: значит, истинно всегда. Останавливают такую программу Ctrl+C: ^C — след нажатия, signal: interrupt — сообщение go run о том, что её прервали. Правило: в теле цикла с условием должно меняться то, от чего оно зависит.

Бывает и нарочно бесконечный цикл — for без заголовка; он крутится, пока его не прервут изнутри (об этом ниже). Строка после него не выполнится никогда: компилятор это пропускает, а go vet находит.

Код для чтения · разбираем, набирать не нужно
	for {
		fmt.Println("Жду заказы")
	}
	fmt.Println("Смена окончена")
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go vet .
main.go:9:2: unreachable code

unreachable code — «недостижимый код»: до строки 9 не дойти.


Попробуйте сейчас: бесконечный цикл и go vet.

Цель: остановить зациклившуюся программу и увидеть unreachable code от go vet.

1. Наберите в scratch/main.go программу без stock -= 12:

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

import "fmt"

func main() {
	stock := 50
	trips := 0
	for stock > 0 {
		trips++
		fmt.Println("Рейс", trips, "остаток", stock)
	}
	fmt.Println("Рейсов:", trips)
}

Запустите и остановите Ctrl+C.

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

2. Замените файл на цикл «Жду заказы»:

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

import "fmt"

func main() {
	for {
		fmt.Println("Жду заказы")
	}
	fmt.Println("Смена окончена")
}

Выполните go vet (запускать не нужно):

▶ Выполните
go vet .

Готово, когда: после Ctrl+C в терминале signal: interrupt, а go vet . печатает строку с unreachable code — пункт t_vet.


break: выйти из цикла

Ситуация. На складе 100 штук, в день уходит 17. Нужен первый день, когда остаток станет меньше 30: в этот день заказывают поставку.

Код для чтения · разбираем, набирать не нужно
	stock := 100
	day := 0
	for {
		day++
		stock -= 17
		if stock < 30 {
			break
		}
	}
	fmt.Println("Заказать поставку на день", day)
	fmt.Println("Остаток в этот день:", stock)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
Заказать поставку на день 5
Остаток в этот день: 15

Как это читать. break сразу выходит из цикла: остаток тела не выполняется, следующих проходов нет, программа идёт за закрывающую }. Условие выхода здесь проверяется в середине тела, после того как день посчитан. Если один цикл стоит в теле другого (course extra 08), break выходит только из того, в котором написан.


continue: к следующему проходу

Ситуация. Заказы приходят по будням, по 40 в день; суббота и воскресенье — выходные.

Код для чтения · разбираем, набирать не нужно
	orders := 0
	for day := 1; day <= 7; day++ {
		if day == 6 || day == 7 {
			continue
		}
		orders += 40
		fmt.Println("День", day, "заказов с начала недели", orders)
	}
	fmt.Println("За неделю:", orders)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
День 1 заказов с начала недели 40
День 2 заказов с начала недели 80
День 3 заказов с начала недели 120
День 4 заказов с начала недели 160
День 5 заказов с начала недели 200
За неделю: 200

Как это читать. continue пропускает остаток тела и переходит к следующему проходу: в цикле с тремя частями — к day++, потом проверка; в цикле с одним условием — сразу к проверке. Поэтому если строка, меняющая условие, стоит после continue, цикл может стать бесконечным.

break continue
остаток тела пропускается пропускается
следующие проходы не будет будут
куда идёт программа за цикл к i++ и проверке (или сразу к проверке)

Попробуйте сейчас: continue и break в одном цикле.

Цель: предсказать, что напечатает программа.

1. Сначала на бумаге: для каждого i — сработал ли continue, сработал ли break, чему стала равна sum.

Код для чтения · разбираем, набирать не нужно
	sum := 0
	for i := 1; i <= 10; i++ {
		if i%2 == 0 {
			continue
		}
		if sum > 10 {
			break
		}
		sum += i
	}
	fmt.Println(sum)

2. Сдать число (вместо ЧИСЛО — своё предсказание):

▶ Выполните · выделенное замените своим
course answer day08.q3 ЧИСЛО

3. Потом проверьте запуском — программа целиком:

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

import "fmt"

func main() {
	sum := 0
	for i := 1; i <= 10; i++ {
		if i%2 == 0 {
			continue
		}
		if sum > 10 {
			break
		}
		sum += i
	}
	fmt.Println(sum)
}
▶ Выполните
cd ~/gocourse/day08/scratch
go run .

Готово, когда: пункт q3 в course check 08 зелёный.


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

Слово while компилятор принимает за имя переменной — отсюда сразу две строки сообщений (код и вывод в course extra 08).

Что видите Что это значит Что делать
unexpected name stock at end of statement и non-declaration statement outside function body написано while for stock > 0 {
вывод не останавливается, потом ^Csignal: interrupt условие не меняется в теле найти, где переменная из условия должна меняться
unreachable code от go vet строка после for { без break не выполнится убрать её или добавить выход
break is not in a loop, switch, or select, continue is not in a loop они стоят вне цикла только внутри for

Дальше: трассировка цикла — course next