---
title: "Цикл for: классическая форма"
description: "Цикл for в Go: счётчик, условие и шаг в одном заголовке. Обратный отсчёт, шаг += 2, off-by-one, вложенные циклы и накопление суммы — с примерами в песочнице."
course: "Go с нуля: базовый синтаксис и конструкции"
url: "https://golangmaster.ru/courses/go-basics/lessons/13-cikl-for/"
published: "2026-09-19"
updated: "2026-09-28"
minutes: 20
---

# Цикл for: классическая форма

*Курс «Go с нуля: базовый синтаксис и конструкции», урок 13 из 25. ~20 минут. Обновлено 28.09.2026. Источник: https://golangmaster.ru/courses/go-basics/lessons/13-cikl-for/*

До сих пор каждая строка наших программ исполнялась максимум один раз: условия [if и else](/courses/go-basics/lessons/11-usloviya-if/) и [switch](/courses/go-basics/lessons/12-switch/) выбирали дорогу, но шли по ней единожды. Настоящие программы повторяют действия: разобрать тысячу строк лога, начислить процент за каждый месяц, проверить каждую строку формы. Для повторов в Go есть ровно один оператор — цикл for: счётчик, условие и шаг собраны в одном заголовке. Опуская части заголовка, из него достаются и «пока»-циклы, и вечные циклы — других циклов в языке нет, и это осознанное решение. Здесь разберём классическую форму: как читается заголовок, сколько раз выполнится тело, где прячется ловушка off-by-one и как собрать таблицу умножения двумя вложенными циклами.

### Циклы в golang: почему цикл один

Представьте кассира, который пробивает чеки: одна и та же последовательность — считать сумму, применить скидку, напечатать квитанцию — повторяется сотню раз за смену. В программе такое повторение записывают циклом: блок кода оборачивается в for, и машина делает скучную часть за человека. Без цикла пришлось бы копировать один и тот же блок сотню раз, а потом синхронно чинить его в сотне мест — потому что в копии №37 условие поправить забыли.

Языки щедры на циклы: где-то есть while, где-то do-while, repeat-until, foreach по коллекции. В Go из этого набора существует только for — и это не экономия, а расчёт на один хорошо продуманный инструмент. Заголовок for собран из трёх частей через точку с запятой, и, опуская части, из него достаются все остальные формы: оставить условие — получится «пока», убрать всё — вечный цикл. Один оператор вместо четырёх: меньше синтаксиса, меньше споров в ревью, одинаковые циклы в коде всей команды.

Классическая форма, с которой начинается любой счёт, выглядит так: `for i := 1; i <= 5; i++ { fmt.Println(i) }` — счётчик, условие и шаг в одном заголовке. Ниже прогоним её в песочнице, посчитаем обороты, соберём таблицу умножения из двух вложенных циклов и разберём ошибки на единицу — off-by-one, самую частую ловушку начинающих.

### Песочница: разминка со счётчиком

Запустите: счётчик пробежит от 1 до 5, и после цикла напечатается финальная строка. Поменяйте старт на i := 3 и условие на i <= 8 — счёт пойдёт с трёх по восемь. Затем сделайте обратный отсчёт: i := 5; i > 0; i--, не забудьте про минус в шаге. Напоследок допишите после цикла fmt.Println(i) и запустите: песочница ответит отказом — об этом разговор ниже, в разделе про область видимости.

```go
package main

import "fmt"

func main() {
        for i := 1; i <= 5; i++ {
                fmt.Println("повторение", i)
        }
        fmt.Println("разминка окончена")
}
```

### Цикл for go: счётчик, условие и шаг

Разложим заголовок `for i := 1; i <= 5; i++` на три части. Первая — инициализация: `i := 1` создаёт счётчик и выполняется ровно один раз, перед первым оборотом. Вторая — условие: `i <= 5` проверяется перед каждым оборотом; истинно — тело работает, ложно — цикл заканчивается. Третья — шаг: `i++` срабатывает после каждого оборота тела и готовит счётчик к следующей проверке.

Читается заголовок одной фразой: «начни с единицы; пока счётчик не больше пяти; после каждого оборота прибавляй единицу». Порядок работы строгий: инициализация, проверка, тело, шаг, снова проверка, снова тело и шаг — и так до первого ложного условия. Условие — обычное булево выражение из сравнений и логики, которые мы собирали в уроке про [bool и логику](/courses/go-basics/lessons/07-bool-i-logika/); ложь в нём завершает цикл так же спокойно, как завершала развилку в if.

Форма повторяет привычки if: круглых скобок вокруг заголовка нет — [gfmt, точнее gofmt, их удалит](/courses/go-basics/lessons/11-usloviya-if/), фигурные скобки обязательны даже для одной строки тела. А `i++` и `i += 2` мы уже разбирали в уроке про [операторы и выражения](/courses/go-basics/lessons/08-operatory/) — здесь они работают шагом цикла. Напомню деталь, которая там же и объяснялась: `i++` — самостоятельный оператор, а не выражение, в арифметику его не вставить.

### Проверьте себя: сколько раз выполнится тело

**Вопрос:** Перед вами цикл for i := 0; i < 3; i++ { fmt.Println(i) }. Сколько строк напечатает программа?

- Две: последняя итерация теряется на проверке условия
- ✅ Три: счётчик примет значения 0, 1 и 2
- Четыре: ноль считается, плюс проверка на тройке
- Бесконечно много: условие всегда истинно

Счётчик стартует с нуля, тело выполняется при i = 0, 1 и 2; на i = 3 проверка 3 < 3 даёт false, и цикл заканчивается. Ловушка — в привычке считать «от нуля до трёх»: чисел на этом отрезке четыре, но тройка телу уже не достаётся. Ошибки на единицу так и зовут off-by-one, и к ним мы ещё вернёмся — это главная тема внимательного чтения заголовков for.

### Off-by-one: сколько оборотов сделает цикл

Вопрос «сколько раз выполнится тело» стоит задавать себе перед каждым запуском — и отвечать подстановкой, а не ощущением. В `for i := 1; i <= 5; i++` счётчик примет значения 1, 2, 3, 4, 5 — пять оборотов. В `for i := 0; i < 5; i++` — значения 0, 1, 2, 3, 4, и снова пять. Обе записи честно дают пять оборотов, но границы у них разные: первая про «от одного до пяти», вторая — про «пять штук начиная с нуля». Смешение стилей и рождает ошибки на единицу: напишете `i <= 5` со стартом с нуля — получите шесть оборотов, `i < 5` со стартом с единицы — четыре.

Правило, которое экономит часы отладки: проверяйте первое и последнее значение счётчика, а не считайте обороты в уме. Подставьте стартовое значение в условие — оно обязано быть истинным. Возьмите значение после последнего шага — оно обязано быть ложным. Две подстановки глазами — и off-by-one поймана до запуска, не дожидаясь тестов и жалоб пользователей на «потерянную последнюю строку».

Шаг, впрочем, не обязан быть единицей: `i += 2` перескакивает через один номер, `i--` гонит счётчик вниз, стартовое значение бывает каким угодно. Это всё тот же цикл — меняется только траектория счётчика; в песочнице ниже шагаем через два.

### Песочница: шаг через два

Запустите: шаг += 2 перескакивает через номер, поэтому бронь получат только нечётные места. Смените старт на seat := 2 — пойдут чётные. Разверните обход: seat := 9; seat > 0; seat -= 2 — и сверьте, что места печатаются от дальнего к ближнему. Затем поставьте шаг seat += 3 и до запуска прикиньте по пальцам, какие места забронируются, — проверьте себя выводом песочницы.

```go
package main

import "fmt"

func main() {
        for seat := 1; seat <= 9; seat += 2 {
                fmt.Println("место", seat, "— забронировано")
        }
}
```

### Что выведет код?

```go
package main

import "fmt"

func main() {
        total := 0
        for i := 1; i <= 3; i++ {
                total += i * 2
        }
        fmt.Println(total)
}
```

**Что выведет программа?**

- 6
- 9
- ✅ 12
- 0

Счётчик принимает 1, 2 и 3; в тело на каждом обороте попадает удвоенное значение: 2, 4, 6. Аккумулятор накапливает: 0 + 2 = 2, затем 2 + 4 = 6, затем 6 + 6 = 12 — она и печатается. total объявлена до цикла, поэтому живёт после него: уберите объявление внутрь фигурных скобок, и на выходе останется только добавка последнего оборота.

### Накопление: аккумулятор за пределами цикла

Половина практических задач с циклами — накопление: сложить числа, собрать строку, посчитать итог. Паттерн один и тот же: переменную объявляют до цикла — `sum := 0`; в теле наращивают — `sum += i`; после цикла используют. Счётчик i умирает вместе с циклом, аккумулятор переживает его — поэтому и место ему снаружи, на уровне функции: это та же дисциплина областей видимости, которую мы разбирали в уроке про [переменные и константы](/courses/go-basics/lessons/04-peremennye-konstanty/).

Классическая ошибка — объявить аккумулятор внутри тела. `sum := 0` в фигурных скобках обнуляется на каждом обороте, и после цикла в переменной остаётся только добавка последнего оборота, а не сумма. Если программа после цикла печатает не то число, первым делом проверьте, где объявлен аккумулятор.

Работает приём не только с числами: `acc += "го "` собирает строку из кусочков — так накапливают логи, разметку, строки отчётов. Механика прежняя: объявить до цикла, наращивать в теле, использовать после.

### Вставьте части заголовка

Соберите заголовок классического цикла: счётчик идёт от единицы до трёх включительно и после каждого оборота растёт на единицу. Готовый цикл должен трижды напечатать строку «итерация» с номером.

```go
___ i := 1; i ___ 3; i___ {
        fmt.Println("итерация", i)
}
```

Подсказка: Первый пропуск — ключевое слово цикла; второй — сравнение «меньше или равно»; третий — постфиксный инкремент, который мы разбирали в уроке про операторы.

Правильные ответы в пропусках: `for`, `<=`, `++`

### Песочница: таблица умножения

Запустите: на каждый оборот внешнего цикла внутренний успевает прокрутиться целиком, поэтому строки идут тройками. Расширьте оба цикла до девяти — получится полная таблица умножения из 81 строки. Поменяйте циклы местами: пусть j станет внешним, а i внутренним — сравните порядок строк с прежним. Затем уберите строку с --- и посмотрите, как сразу теряется граница между тройками.

```go
package main

import "fmt"

func main() {
        for i := 1; i <= 3; i++ {
                for j := 1; j <= 3; j++ {
                        fmt.Printf("%d × %d = %d\n", i, j, i*j)
                }
                fmt.Println("---")
        }
}
```

### Вложенные циклы: внешний и внутренний

Тело цикла — обычный блок, и в нём может стоять ещё один for. Внешний цикл берёт очередное значение i и на каждый свой оборот прокручивает внутренний целиком, от старта до финиша. В таблице выше внешний оборот — это множитель, внутренний перебирает три множимых, поэтому строк девять: три оборота внешнего умножить на три оборота внутреннего. Печать через Printf с глаголами %d — из урока про [fmt: вывод и форматирование](/courses/go-basics/lessons/09-fmt-vyvod/).

Число оборотов вложенных циклов перемножается, и прикинуть произведение стоит до запуска: 9 × 9 = 81 строка — норм, а два цикла по 10 000 — сто миллионов оборотов, и на таком замедляется не только песочница, но и настоящий сервер. Когда цикл в цикле кажется естественным решением, посчитайте произведение на бумажке: возможно, работу стоит перевернуть или раздробить.

Счётчикам давайте внятные имена: i и j привычны, но в задаче про недели и дни week и day читаются без комментариев. Области видимости здесь те же, что у переменных в заголовке if: week живёт во внешнем цикле, day — во внутреннем, перепутать их не выйдет — компилятор не даст. Когда очередей станет слишком много и работу захочется выполнять не по очереди, а одновременно — это уже другая глава: в [курсе go-advanced](/courses/go-advanced/) для этого есть горутины.

Одна оговорка наперёд: break внутри вложенного цикла завершает только свой, внутренний цикл. Полный разговор о break, continue и вечных циклах — тема следующего урока.

### Шпаргалка: части заголовка for

| Часть заголовка | Когда срабатывает | Пример |
| --- | --- | --- |
| Инициализация | Один раз перед первым оборотом | i := 1 |
| Условие | Перед каждым оборотом; false завершает цикл | i <= 5 |
| Шаг (post) | После каждого оборота тела | i++ или i += 2 |
| Тело | Каждый оборот, пока условие истинно | sum += i |
| Все части опущены | Вечный цикл, выход через break | for { } — тема следующего урока |

### For как while: опущенные части заголовка

Каждая из трёх частей заголовка опускается — на это и делалась ставка, когда в языке оставили один цикл. Уберите шаг: `for i := 0; i < 3; { … i++ }` — шаг перекочевал в тело, и цикл по-прежнему считает три оборота; песочница такую форму принимает, проверьте на любом примере выше.

Самая ходовая форма — «только условие», она же «пока»: `count := 3; for count > 0 { fmt.Println(count); count-- }`. Точек с запятой нет вовсе: когда от заголовка остаётся одно условие, for превращается в while из других языков. Так пишут циклы «пока не кончатся попытки», «пока очередь не опустеет», «пока пользователь не введёт стоп» — шаг в них обычно живёт в теле и часто зависит от того, что произошло внутри оборота.

Если опустить всё — `for { }` — получится вечный цикл: условия нет, значит, и лгать нечему. Выход организуют внутри тела оператором break; в песочнице конструкция работает, а без выхода упирается в страховку, о которой ниже. Одна честная оговорка про песочницу: редкая форма без счётчика, но с ведущей точкой с запятой — `for ; cond; post` — законна в настоящем Go, и основной движок песочницы её исполняет, а вот резервный упрощённый движок её не парсит, поэтому в уроках мы пишем счётчик с объявлением до цикла — читается так же, а работает везде. А break, continue и осмысленные вечные циклы — следующая остановка курса: разберём, как выходить из `for { }` красиво и где бесконечный цикл вообще уместен.

### Красивый приём: два счётчика навстречу

```go
package main

import "fmt"

func main() {
        for i, j := 0, 9; i < j; i, j = i+1, j-1 {
                fmt.Println(i, j)
        }
}
```

Этот код запускается в песочнице — прогоните. В заголовке можно объявить сразу две переменные: `i, j := 0, 9`, а шаг обновляет обе — `i, j = i+1, j-1`. Счётчики идут навстречу друг другу, и цикл заканчивается, когда i догоняет j: пары 0–9, 1–8, 2–7, 3–6, 4–5. Такой приём любят в задачах про палиндромы и поиск пары чисел с заданной суммой. Заметьте: обе переменные видны только внутри цикла — после него не существует ни i, ни j.

> **Типичная ошибка: цикл, который никогда не кончается.** Вечный цикл чаще всего делают три опечатки: забыт шаг (счётчик не меняется), шаг направлен не туда (i-- при условии i < n) и условие, мимо которого шаг проскакивает: `for i := 0; i != 10; i += 3` печатает 0, 3, 6, 9, 12, 15 и никогда не останавливается — десятка пропущена. На настоящем компьютере программа зависнет и будет греть процессор, пока её не убьют. В песочнице на этот случай страховка: программу прервёт лимит — времени, шагов или объёма вывода, смотря что первым исчерпается (например, «превышен лимит времени») — можете убедиться, вставив тот же заголовок в любой runner выше. Правило на каждый день: шаг обязан приближать счётчик к границе, а `!=` для счёта с крупным шагом почти всегда ошибка — пишите < или <=.

### Соберите программу: посчитаем итог

Соберите программу, которая накапливает сумму чисел от 1 до 4: аккумулятор объявляется до цикла, наращивается в теле, печать идёт после цикла. Правильная программа выводит «итого: 10».

Правильный порядок строк:

1. `package main`
2. `import "fmt"`
3. `func main() {`
4. `total := 0`
5. `for i := 1; i <= 4; i++ {`
6. `total += i`
7. `}`
8. `fmt.Println("итого:", total)`
9. `}`

### Карточки: цикл for и его заголовок

- **Сколько циклов в Go и зачем один?** — Один — for. Заголовок из трёх частей (счётчик; условие; шаг) покрывает классический счёт, а опущением частей получаются «пока»-циклы и вечный цикл. Один оператор — меньше синтаксиса и разночтений.
- **Когда выполняется каждая часть заголовка?** — Инициализация — один раз перед первым оборотом; условие — перед каждым оборотом, при false цикл заканчивается; шаг — после каждого оборота тела. Порядок строгий: init, проверка, тело, шаг, проверка…
- **Сколько оборотов у for i := 0; i < n; i++?** — Ровно n: счётчик пробегает 0 … n-1. Форма for i := 1; i <= n; i++ даёт те же n оборотов, но от 1 до n. Смешение этих двух стилей — главный источник off-by-one.
- **Где объявлять переменную-аккумулятор?** — До цикла, на уровне функции: счётчик из заголовка умирает вместе с циклом, а аккумулятор должен его пережить. Объявленный в теле аккумулятор обнуляется на каждом обороте — классическая ошибка.
- **Что с областью видимости счётчика?** — i из заголовка for i := ... видна только внутри цикла; обращение после цикла — ошибка «неопределённая переменная». Нужен счётчик после цикла — объявляйте его до цикла обычным :=.
- **Как написать «пока» (while) на Go?** — for cond { ... }: в заголовке остаётся только условие, без точек с запятой; шаг живёт в теле. Полностью пустой for { } — вечный цикл, выход через break (подробно — следующий урок).

## Коротко: что запомнить

- В Go один цикл — for; классическая форма for i := 1; i <= 5; i++ { … } собирает счётчик, условие и шаг в одном заголовке.
- Инициализация выполняется один раз перед первым оборотом, условие проверяется перед каждым оборотом, шаг срабатывает после тела.
- for i := 0; i < n; i++ делает ровно n оборотов: счётчик пробегает от 0 до n-1; off-by-one ловится проверкой первого и последнего значения счётчика.
- Счётчик из заголовка виден только внутри цикла; аккумулятор объявляют до цикла — объявленный в теле, он обнуляется каждый оборот.
- Шаг — любой оператор присваивания: i++, i--, i += 2; главное, чтобы шаг приближал счётчик к границе, иначе цикл не закончится.
- Вложенные циклы перемножают обороты: на каждый оборот внешнего внутренний прокручивается целиком.
- Части заголовка опускаются: for cond { … } — «пока», for { } — вечный цикл с выходом через break; break и continue подробно разбираются в следующем уроке.

## Домашнее задание

Соберите план тренировок: внешний цикл for week := 1; week <= 4; week++ печатает «неделя N», внутри вложенный for day := 1; day <= 3; day++ печатает «день M: тренировка». Проверьте, что строк ровно двенадцать. Затем добавьте аккумулятор minutes := 0 до внешнего цикла и наращивайте его на 45 в теле внутреннего; после всех циклов напечатайте «итого минут: 540». Наконец, допишите третий цикл for stretch := 2; stretch <= 10; stretch += 2 с печатью «растяжка N» — убедитесь, что номера вышли только чётные.

## Вопросы и ответы по уроку

### Почему в Go нет while и do-while?

Заголовок for покрывает эти формы опущением частей: for cond { … } — это и есть while, а do-while собирается из for { … } с проверкой условия в конце тела и выходом через break. Один оператор вместо трёх: меньше синтаксиса, и слово while в Go-коде не встретится в принципе.

### Как посчитать обороты, если и старт, и граница — переменные?

Подставьте крайние значения: счётчик начинает со start и работает, пока условие истинно. Для for i := start; i < end; i++ это end - start оборотов, для i <= end — end - start + 1. Сомневаетесь — напечатайте счётчик в первом и последнем обороте и сверьте границы: подстановка краёв ловит почти все ошибки на единицу.

### Что будет, если в пост-части написать i + 1 вместо i++?

Пост-часть — это оператор, а не выражение: i++ и i += 1 меняют переменную, а i + 1 лишь вычисляет значение и никуда его не кладёт. Настоящий компилятор отклонит такой заголовок, а песочница пропустит: цикл станет вечным, печать — бесконечной, и её оборвёт лимит строк вывода. Рабочие шаги: i++, i += n или присваивание i = i + 1.
