Урок курса

Цикл for: три формы, break и continue

Golang с нуля

В прошлом уроке мы разбирали if и switch — инструменты для ветвления. Теперь добавим повторение: for в Go — единственный цикл, но он достаточно гибок, чтобы заменить и while, и бесконечный цикл с флагом выхода.

Три формы цикла for: синтаксис и механизм выполнения

Go не имеет while или do-while — только for. Зато у него три формы записи, и каждая уместна в своей ситуации.

Форма с инициализатором, условием и постшагом — то, что в других языках называют классическим for:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Порядок выполнения фиксирован: сначала один раз выполняется инициализатор i := 0, затем перед каждой итерацией проверяется условие i < 5, и только если оно истинно — выполняется тело. После тела — постшаг i++, и снова проверка условия. Когда условие становится ложным, цикл завершается.

Важная деталь: переменная i, объявленная через := в инициализаторе, существует только внутри цикла. За его пределами она недоступна. Это не ограничение, а разумная область видимости — счётчик цикла редко нужен после его завершения.

Ещё одна особенность Go: i++ — это инструкция, а не выражение. Написать j := i++ не получится — компилятор откажет. Это сделано намеренно, чтобы устранить целый класс ошибок вроде a[i++] = i++.

Форма только с условием — аналог while:

n := 1
for n < 100 {
    n *= 2
}
fmt.Println(n) // 128

Здесь нет инициализатора и постшага — только условие. Переменная n объявлена до цикла, потому что её значение нужно после выхода.

Бесконечный цикл — форма вообще без условия:

for {
    // выполняется бесконечно
}

Выйти из него можно только явно: через break, return или в экстремальных случаях os.Exit. Именно эта форма удобна, когда условие выхода проверяется не в заголовке цикла, а где-то в середине тела — что и понадобится в итоговом примере урока.

Во всех трёх формах тело цикла обязательно заключено в фигурные скобки — без исключений, как и в if.

break и continue: досрочный выход и пропуск итерации

break и continue управляют ходом выполнения цикла, но делают это по-разному.

break немедленно завершает ближайшую охватывающую конструкцию — это может быть for, switch или select. Важно: если break стоит внутри switch, вложенного в цикл, он завершит switch, а не цикл. Мы разбирали switch в прошлом уроке — теперь это различие становится практически значимым.

continue, в отличие от break, относится только к циклу — к ближайшему охватывающему for. Он прерывает текущую итерацию и переходит к следующей. Но «следующая итерация» — это не просто начало тела. В форме с постшагом for i := 0; i < n; i++ после continue сначала выполняется i++, и только затем проверяется условие. Это принципиально.

for i := 0; i < 5; i++ {
    if i == 2 {
        continue
    }
    fmt.Println(i)
}

Вывод:

0
1
3
4

Когда i равно 2, continue пропускает fmt.Println, но i++ всё равно выполняется — поэтому следующая итерация начнётся с i = 3, а не зависнет на i = 2. Цикл не стал бесконечным.

for i := 0; i < 5; i++ {
    if i == 3 {
        break
    }
    fmt.Println(i)
}

Вывод:

0
1
2

Как только i достигает 3, цикл завершается полностью — постшаг не выполняется, условие не проверяется.

Если циклы вложены, оба оператора без метки действуют на внутренний цикл — тот, в теле которого они стоят. Внешний цикл продолжается. Это отдельно от ситуации со switch: break внутри switch всегда завершает switch, даже если снаружи есть цикл — на цикл такой break не влияет.

Бесконечный цикл с break: суммирование чисел до ввода 0

Соберём всё вместе в задаче: читать числа с клавиатуры и накапливать их сумму, пока пользователь не введёт 0.

package main

import "fmt"

func main() {
    var sum, n int
    for {
        fmt.Scan(&n)
        if n == 0 {
            break
        }
        sum += n
    }
    fmt.Println(sum)
}

Пример работы:

3
7
-2
0
8

Обе переменные объявлены до цикла через var sum, n int, но по разным причинам.

sum — накопитель, и его объявление снаружи обязательно по двум причинам. Первая: значение должно сохраняться между итерациями. Если написать sum := 0 внутри тела цикла, на каждой итерации создаётся новая переменная с нулевым начальным значением — всё накопленное за предыдущие итерации теряется. Вторая: sum нужна после завершения цикла — в строке fmt.Println(sum). Переменная, объявленная через := внутри тела for, недоступна за его пределами; программа с sum внутри цикла попросту не скомпилировалась бы.

n — другой случай. fmt.Scan(&n) принимает адрес переменной, но само по себе это не требует объявлять n вне цикла — var n int или n := 0 внутри тела тоже допустимы синтаксически. Внешнее объявление здесь — вопрос удобства: переменная повторно используется на каждой итерации без лишних объявлений.

Почему выбрана бесконечная форма for, а не for n != 0? Потому что условие выхода проверяется уже после чтения нового значения n, то есть в середине тела. Форма только с условием потребовала бы либо читать первое число до цикла, либо дублировать логику. Бесконечный for с break внутри выражает намерение прямее: «читай до тех пор, пока сам не решишь остановиться».

Попробуйте решить

Что выведет следующая программа?

package main

import "fmt"

func main() {
    sum := 0
    for i := 1; i <= 5; i++ {
        if i == 3 {
            continue
        }
        sum += i
    }
    fmt.Println(sum)
}

Продолжить с проверкой и прогрессом

Откройте интерактивный раннер с заданиями урока.

Перейти к интерактивному уроку