Цикл for как while и бесконечный цикл

Типы циклов в программировании

В большинстве языков программирования существует три типа циклов:

  1. Цикл со счётчикомfor (классический)
  2. Цикл с предусловиемwhile
  3. Цикл с постусловиемdo-while

Особенность Go: нет отдельных операторов while и do-while. Вместо этого один оператор for может работать во всех трёх режимах.


For как while: цикл с предусловием

Синтаксис

// Классический for
for init; condition; post {
    // тело
}

// For как while (опускаем init и post)
for condition {
    // тело
}

Трансформация из классического for в while

Шаг 1: Классический for

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

Шаг 2: Выносим инициализацию наружу

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

Шаг 3: Выносим приращение в тело

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

Шаг 4: Убираем точки с запятой → получаем while

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

Пример: обратный отсчёт

countdown := 5
for countdown >= 0 {
    fmt.Printf("%d ", countdown)
    countdown--
}
// Вывод: 5 4 3 2 1 0

Порядок выполнения:

  1. Инициализация countdown = 5 (вне цикла)
  2. Проверка условия: 5 >= 0 → true
  3. Печать 5
  4. Приращение: countdown--countdown = 4
  5. Проверка условия: 4 >= 0 → true
  6. Повторение до countdown = -1 → выход

Когда использовать for как while

Ситуация 1: Неизвестное количество итераций

sum := 0
iterations := 0

for sum < 50 {
    value := rand.Intn(10)  // Случайное 0-9
    sum += value
    iterations++
    fmt.Printf("Итерация %d: +%d → сумма = %d\n", iterations, value, sum)
}

Пример вывода:

Итерация 1: +3 → сумма = 3
Итерация 2: +7 → сумма = 10
Итерация 3: +8 → сумма = 18
Итерация 4: +9 → сумма = 27
Итерация 5: +6 → сумма = 33
Итерация 6: +2 → сумма = 35
Итерация 7: +8 → сумма = 43
Итерация 8: +9 → сумма = 52

Цикл завершится, когда sum >= 50, но заранее неизвестно, сколько потребуется итераций.

Ситуация 2: Обработка пользовательского ввода

attempts := 0
maxAttempts := 3

for attempts < maxAttempts {
    randomNum := rand.Intn(100)
    attempts++
    fmt.Printf("Попытка %d: число %d\n", attempts, randomNum)

    if randomNum > 80 {
        fmt.Println("✓ Успех!")
        break  // Досрочный выход
    }
}

Пример вывода:

Попытка 1: число 34
Попытка 2: число 67
Попытка 3: число 89
✓ Успех!

Сравнение: Go vs другие языки

Язык Синтаксис while Синтаксис do-while
Java/C#/C++ while (x > 0) { } do { } while (x > 0);
Python while x > 0: ❌ Нет
JavaScript while (x > 0) { } do { } while (x > 0);
Go for x > 0 { } ❌ Нет (эмулируется)

Философия Go: минимализм — один оператор for для всех случаев.


Бесконечный цикл: for { }

Синтаксис

for {
    // тело цикла
    // выход только через break
}

Пример: печать символов

for {
    fmt.Print("+")
}
// Вывод: ++++++++++++++++... (бесконечно)
// Остановка: Ctrl+C

Выход через break

count := 0
for {
    count++
    fmt.Printf("Итерация %d\n", count)

    if count >= 3 {
        fmt.Println("Выходим через break")
        break  // Выход из цикла
    }
}

Вывод:

Итерация 1
Итерация 2
Итерация 3
Выходим через break

Эмуляция do-while (цикл с постусловием)

Что такое do-while

Do-while — цикл, который сначала выполняет тело, затем проверяет условие.

Классический do-while (Java/C#):

do {
    // тело
} while (condition);

Эмуляция в Go

i := 1
for {
    fmt.Print("+")  // Тело выполняется СНАЧАЛА
    i++
    
    if i >= 5 {     // Проверка ПОСЛЕ тела
        break
    }
}
// Вывод: ++++ (4 плюса)

Порядок выполнения:

  1. Печать + (i=1)
  2. Приращение i++ → i=2
  3. Проверка i >= 5 → false → продолжение
  4. Печать + (i=2)
  5. Приращение i++ → i=3
  6. Проверка i >= 5 → false → продолжение
  7. Печать + (i=3)
  8. Приращение i++ → i=4
  9. Проверка i >= 5 → false → продолжение
  10. Печать + (i=4)
  11. Приращение i++ → i=5
  12. Проверка i >= 5 → true → break → выход

Нужно ли это использовать?

Ответ: скорее всего, нет.

Причины:

  • Код сложно читать
  • 95-99% задач решаются классическим for
  • Эмуляция do-while не идиоматична для Go

Оператор break: ручник цикла

Назначение

break немедленно прерывает выполнение цикла.

Пример: остановка при числе > 7

j := 0
for j < 10 {
    randomVal := rand.Intn(10)
    fmt.Printf("%d ", randomVal)

    if randomVal > 7 {
        fmt.Println("\nЧисло > 7, выходим!")
        break  // Выход из цикла
    }
    j++
}

Пример вывода:

3 5 2 8 
Число > 7, выходим!

Работа с дебаггером

Трассировка:

Итерация j randomVal Условие > 7 Действие
1 0 3 false Продолжение
2 1 5 false Продолжение
3 2 2 false Продолжение
4 3 8 true break → выход

После break всё, что следует в теле цикла, игнорируется.


Оператор continue: пропуск итерации

Назначение

continue пропускает остаток текущей итерации и переходит к следующей.

Пример: печать только нечётных чисел

i := 0
for i < 10 {
    i++
    if i%2 == 0 {
        continue  // Пропускаем чётные
    }
    fmt.Printf("%d ", i)
}
// Вывод: 1 3 5 7 9

Трассировка

i i%2 == 0 Действие Вывод
1 false Печать 1
2 true continue → пропуск
3 false Печать 3
4 true continue → пропуск
5 false Печать 5
6 true continue → пропуск
7 false Печать 7
8 true continue → пропуск
9 false Печать 9
10 Выход из цикла

Визуализация continue

for i := 1; i <= 5; i++ {
    if i == 3 {
        continue  // Пропуск
    }
    fmt.Println(i)
}

Порядок:

  1. i=1 → печать 1
  2. i=2 → печать 2
  3. i=3 → continueпропуск fmt.Println(3)
  4. i=4 → печать 4
  5. i=5 → печать 5

Вывод:

1
2
4
5

Опасность: бесконечный цикл с continue

Проблемный код

i := 0
for i < 10 {
    if i%2 == 0 {
        continue  // ❌ ОШИБКА: i никогда не увеличивается!
    }
    fmt.Println(i)
    i++
}

Что происходит:

  1. i=0
  2. 0%2 == 0 → true
  3. continue → возврат к проверке условия
  4. i=0 (не изменился!)
  5. 0%2 == 0 → true
  6. continue → возврат к проверке условия
  7. Бесконечный цикл

Отладка с дебаггером

Шаги:

  1. Поставить breakpoint на for i < 10
  2. Запустить Debug
  3. Step Over (F10) → проверка условия
  4. Step Over → проверка i%2 == 0 → true
  5. Step Over → continue
  6. Step Over → снова проверка условия (i не изменился!)
  7. Повторение бесконечно

Вывод в терминале:

......................................
(точки печатаются бесконечно)

Правильный код

i := 0
for i < 10 {
    i++  // ✅ Приращение ДО continue
    if i%2 == 0 {
        continue
    }
    fmt.Println(i)
}

Практический пример: игра “Угадай число”

Код

secretNumber := rand.Intn(10) + 1  // Загадано число 1-10
guesses := 0

for {
    guesses++
    guess := rand.Intn(10) + 1
    fmt.Printf("Попытка %d: угадываем число %d... ", guesses, guess)

    if guess == secretNumber {
        fmt.Printf("✓ Правильно! (число было %d)\n", secretNumber)
        break  // Выход из цикла
    } else {
        fmt.Println("✗ Неправильно")
    }

    if guesses >= 5 {
        fmt.Printf("✗ Не угадали за 5 попыток (было %d)\n", secretNumber)
        break  // Выход после 5 попыток
    }
}

Пример вывода (успех)

Попытка 1: угадываем число 3... ✗ Неправильно
Попытка 2: угадываем число 7... ✗ Неправильно
Попытка 3: угадываем число 5... ✓ Правильно! (число было 5)

Пример вывода (неудача)

Попытка 1: угадываем число 2... ✗ Неправильно
Попытка 2: угадываем число 9... ✗ Неправильно
Попытка 3: угадываем число 4... ✗ Неправильно
Попытка 4: угадываем число 1... ✗ Неправильно
Попытка 5: угадываем число 8... ✗ Неправильно
✗ Не угадали за 5 попыток (было 7)

Анализ

Почему бесконечный цикл?

  • Количество попыток заранее неизвестно
  • Выход через break при угадывании или превышении лимита

Два условия выхода:

  1. guess == secretNumber → успех
  2. guesses >= 5 → лимит попыток

Когда использовать каждую форму

Классический for (95-99% задач)

for i := 0; i < 10; i++ {
    // Известное количество итераций
}

Применение:

  • Обход массивов/слайсов по индексу
  • Фиксированное количество повторений
  • Итерации с шагом

For как while

for condition {
    // Неизвестное количество итераций
}

Применение:

  • Чтение файлов до конца
  • Обработка пользовательского ввода
  • Ожидание события
  • Алгоритмы с неопределённым числом шагов

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

for {
    // Работа до break
}

Применение:

  • Игровые циклы
  • Серверные приложения (обработка запросов)
  • Мониторинг событий
  • Интерактивные CLI-приложения

Рекомендации по использованию

✅ Хорошие практики

  1. Используйте классический for, если количество итераций известно
  2. Используйте for-while, если количество итераций зависит от условия
  3. Бесконечный цикл обязательно должен иметь условие выхода (break)
  4. Избегайте break/continue, если задача решается без них
  5. Приращение счётчика делайте ДО continue, иначе бесконечный цикл

❌ Плохие практики

  1. Не эмулируйте do-while — код становится нечитаемым
  2. Не используйте break/continue без необходимости — усложняет логику
  3. Не создавайте бесконечные циклы без выхода — программа зависнет
  4. Не размещайте приращение после continue — риск бесконечного цикла

Сравнение операторов break и continue

Характеристика break continue
Действие Выход из цикла Переход к следующей итерации
Что пропускается Весь оставшийся цикл Остаток текущей итерации
Проверка условия Нет (выход) Да (перед новой итерацией)
Постусловие (post) Нет Да (выполняется перед проверкой)
Применение Досрочное завершение Пропуск особых случаев

Пример различия

for i := 1; i <= 5; i++ {
    if i == 3 {
        break  // Выход: печатает 1, 2
    }
    fmt.Println(i)
}
for i := 1; i <= 5; i++ {
    if i == 3 {
        continue  // Пропуск: печатает 1, 2, 4, 5
    }
    fmt.Println(i)
}

Ключевые моменты

  1. Go имеет только один цикл — for
  2. For может работать как while: for condition { }
  3. Бесконечный цикл: for { } с обязательным break
  4. Break: немедленный выход из цикла
  5. Continue: пропуск остатка итерации, переход к следующей
  6. Приращение ДО continue, иначе бесконечный цикл
  7. Используйте классический for в 95-99% случаев
  8. Бесконечный цикл требует условия выхода, иначе программа зависнет
  9. Избегайте break/continue без необходимости — усложняют код
  10. Дебаггер помогает понять порядок выполнения и найти бесконечные циклы