Перейти к содержимому
Теория · Циклы · урок 1 из 5

Цикл for-in

Чему вы научитесь

Чему вы научитесь:

  • повторять код циклом for-in по диапазону и по ряду чисел из stride;
  • выбирать между while и repeat-while и предсказывать, сколько раз выполнится тело цикла;
  • выходить из цикла через break и пропускать круг через continue;
  • выходить сразу из двух вложенных циклов через break с меткой.
Беговая дорожка с табличкой 1...3 на старте, выходом break наружу и короткой дорожкой continue к старту

Чтобы напечатать номера кругов с первого по сотый, пришлось бы написать сто строк с print. Так никто не делает. Для повторяющихся действий есть цикл — конструкция, которая выполняет блок кода снова и снова.

Представьте беговую дорожку. Тренер говорит бегуну: «Пробеги круги с первого по третий». Бегун пробегает первый круг, потом второй, потом третий. Когда круги из задания кончились, тренировка завершена.

Цикл for-in работает так же. Ему заранее называют номера кругов, и он выполняет свой блок кода — тело цикла — по разу для каждого номера:

SWIFT
for lap in 1...3 {
    print("lap", lap, terminator: "; ") // lap 1; lap 2; lap 3;
}

Давайте разберём, что здесь происходит:

  1. 1...3 — закрытый диапазон из темы про операторы: числа 1, 2 и 3.
  2. На первом круге в lap лежит 1, и тело печатает lap 1.
  3. На втором круге в lap лежит 2, на третьем — 3.
  4. Номера кончились, и цикл завершается.

terminator: "; " ставит после каждого круга точку с запятой вместо перевода строки, поэтому все три круга печатаются в одну строку.

Как устроен for-in

Заголовок for lap in 1...3 читается как «для каждого lap из 1...3». Его части:

  • for — ключевое слово, с которого начинается цикл;
  • lap — имя, под которым тело видит номер текущего круга;
  • in и диапазон после него — номера, которые цикл пройдёт по порядку;
  • фигурные скобки — тело цикла, то есть код, который повторяется.

Один проход тела называют итерацией. Цикл for lap in 1...3 делает три итерации, по одной на каждое число диапазона.

Полуоткрытый диапазон тоже подходит. Цикл for i in 0..<3 выполнит тело для 0, 1 и 2: так удобно повторить действие ровно три раза.

Номер круга нельзя изменить

Имя lap ведёт себя как константа let: на каждой итерации Swift создаёт его заново и кладёт в него следующий номер. Изменить его нельзя: если написать в теле lap += 1, программа не скомпилируется.

TEXT
error: left side of mutating operator isn't mutable: 'lap' is a 'let' constant

Здесь картинка с дорожкой неточна. Живой бегун может передумать и пробежать лишний круг. У for-in номера кругов заданы ещё до старта, и тело цикла их не меняет.

Имя lap живёт только внутри фигурных скобок. Строка print(lap) после цикла даст ошибку cannot find 'lap' in scope.

Когда номер не нужен

Иногда тело не использует номер круга. Тогда вместо имени пишут _ — знак «значение мне не нужно»:

SWIFT
var meters = 0
for _ in 1...4 {
    meters += 400
}
print(meters) // 1600

Бегун пробежал четыре круга по 400 метров, и программа печатает 1600. Номер конкретного круга здесь ни к чему: важно только, сколько кругов.

Если написать for i in 1...4 и не использовать i в теле, программа запустится, но компилятор предупредит:

TEXT
warning: immutable value 'i' was never used; consider replacing with '_' or removing it

Шаг больше единицы

Диапазон всегда идёт с шагом 1. Чтобы перебрать числа через одно, нужен stride: он строит ряд чисел от начала с заданным шагом. После from: пишут начало ряда, после by: — шаг, а конец задают одним из двух способов:

SWIFT
for n in stride(from: 0, to: 10, by: 2) {
    print(n, terminator: " ") // 0 2 4 6 8
}
print()
for n in stride(from: 0, through: 10, by: 2) {
    print(n, terminator: " ") // 0 2 4 6 8 10
}

Пустой print() печатает только перевод строки, поэтому второй ряд начинается с новой.

stride(from:to:by:)

С to: конец в ряд не входит, как у полуоткрытого диапазона ..<. Ряд от 0 с шагом 2 доходит до 8. Следующее число 10 равно концу, поэтому в ряд не попадает.

stride(from:through:by:)

С through: конец входит в ряд, как у закрытого диапазона ..., поэтому второй цикл печатает и 10. Если шаг не попадает точно в конец, ряд останавливается раньше: stride(from: 0, through: 9, by: 2) даёт 0 2 4 6 8.

Запомнить разницу помогает перевод: to значит «до», а through — «до конца включительно».

Частая ошибка

Обратный отсчёт написали через диапазон:

SWIFT
for n in 3...1 { // ✗
    print(n)
}
TEXT
Fatal error: Range requires lowerBound <= upperBound

Программа компилируется, но при запуске аварийно завершается ещё до первой итерации: начало диапазона не может быть больше конца. Для обратного отсчёта нужен stride с отрицательным шагом: stride(from: 3, through: 1, by: -1) даёт 3 2 1.

Главное

  • for-in выполняет тело по разу для каждого числа диапазона или ряда stride, и каждый такой проход — итерация.
  • Имя из заголовка цикла — константа: она получает новое значение на каждой итерации, и менять её в теле нельзя. _ ставят вместо имени, когда значение не нужно.
  • stride(from:to:by:) не включает конец, а stride(from:through:by:) включает его, если шаг на него попадает.
  • Обратный отсчёт — это stride с отрицательным шагом: диапазон 3...1 аварийно завершает программу.