JavaRush /Курси /Go SELF /Єдиний цикл у Go — for

Єдиний цикл у Go — for

Go SELF
Рівень 4 , Лекція 1
Відкрита

1. Навіщо потрібні цикли та чому в Go цикл лише один

Якщо ви хоч раз намагалися «просто повторити дію 10 разів» вручну, то знаєте, чим це зазвичай закінчується: десять однакових рядків, а на одинадцятому ви помиляєтеся в імені змінної. Цикл — це спосіб сказати компʼютеру: «Повтори ось цей блок коду кілька разів і зупинися, коли настане час».

Цикл у реальному житті: поки Барні в барі, Мо його виганяє.

З погляду здорового глузду, цикл — це керувальна конструкція, яка повторює тіло (блок { ... }), доки виконується певне правило. У Go це особливо зручно: цикл тут один — for. Окремого слова while немає. Наче сам Go каже: «Навіщо два слова, якщо вистачає одного?»

for у Go: одна конструкція для різних задач

Фраза «єдиний цикл» не означає, що Go чимось обмежений. Навпаки, for уміє кілька форм запису. Але сьогодні ми розберемо найкласичнішу й найкориснішу для новачка — цикл з лічильником.

На практиці це виглядає так: «зроби 5 разів», «пройди від 1 до N», «виведи числа від 0 до 9», «прочитай N значень». Майже всі такі задачі зручно записати класичним for, де є початок, умова і крок.

2. Класичний for: init; cond; post

Загальна форма циклу

Ця форма схожа на простий бутерброд: три шари з чіткими ролями. Виглядає вона так:

for init; cond; post {
    // тіло циклу
}

У Go ви бачитимете цю форму постійно — навіть в офіційних прикладах, наприклад у матеріалах про defer, де всередині циклу відкладають виведення значень. Саме там часто використовують звичний лічильник i := 0; i < 4; i++ — тобто саме ту конструкцію, яку ми зараз розбираємо.

Тепер розкладемо її по частинах.

init: підготовка перед стартом

У секції init зазвичай створюють і ініціалізують лічильник. Причому це роблять один раз — перед першою перевіркою умови.

Найчастіше там пишуть i := 0 або i := 1. Чому іноді 0, а іноді 1? Це залежить від зручності й від того, як саме ви рахуєте подумки. Компʼютеру байдуже, а людині — ні. Якщо ви нумеруєте «спроби 1..N», зазвичай стартують із 1. Якщо ви працюєте з індексами, що ми пізніше побачимо в масивах і слайсах, часто стартують із 0.

Невеликий приклад: оголосили лічильник і підготувалися до виведення.

package main

import "fmt"

func main() {
	for i := 0; i < 3; i++ {
		fmt.Println("Ітерація", i) // Ітерація 0 / 1 / 2
	}
}

Зверніть увагу на деталь: i оголошено через := прямо всередині for. Це нормально й дуже типово: лічильник живе всередині циклу і майже ніколи не потрібен зовні.

cond: умова зупинки

cond (condition) — це вираз типу bool, який перевіряється перед кожною ітерацією. Якщо умова істинна — виконується тіло циклу. Якщо хибна — цикл завершується, і програма продовжує виконання після циклу.

Подумайте про це так: умова — це охоронець на вході. Він не пускає вас на чергову ітерацію, якщо правило вже не виконується.

Приклад: виведемо числа 1..5 включно:

package main

import "fmt"

func main() {
	for i := 1; i <= 5; i++ {
		fmt.Println(i) // 1 2 3 4 5
	}
}

Тут умова i <= 5 означає: «поки i не перевищує 5 — працюємо».

Тіло циклу: що саме повторюємо

Тіло циклу — це звичайний блок коду у фігурних дужках. Ви вже бачили такі блоки в if/else і switch. У циклі цей блок виконується багато разів — і це його головна відмінність.

Під час написання циклу краще тримати тіло максимально простим. Якщо всередині занадто багато дій, важко зрозуміти, скільки разів і що саме відбувається. Поки ви тільки починаєте, пишіть короткими й зрозумілими фрагментами, а громіздкі конструкції залишимо тим, хто любить страждати.

Найпростіший приклад тіла — вивести щось кілька разів.

package main

import "fmt"

func main() {
	for i := 0; i < 2; i++ {
		fmt.Println("Go — це нормально") // Go — це нормально (2 рази)
	}
}

post: крок між ітераціями

post виконується після тіла на кожній ітерації. Це те місце, де зазвичай змінюють лічильник: збільшують або зменшують його, щоб цикл у якийсь момент завершився.

Якщо ви забудете змінювати лічильник або зробите це не в тому місці, цикл може стати нескінченним. Але панікувати не варто: це типова помилка новачка, і ми навчимося помічати її з першого погляду.

Невеликий приклад — крок на 2.

package main

import "fmt"

func main() {
	for i := 0; i < 6; i = i + 2 {
		fmt.Println(i) // 0 2 4
	}
}

Тут post — це i = i + 2. Він виконується після кожного fmt.Println.

i++ і i--: короткий запис для «плюс один» і «мінус один»

У Go для лічильників є два дуже впізнавані оператори зі світу класичних циклів: i++ і i--. Це короткий запис для «збільшити на 1» і «зменшити на 1».

Під час читання коду важливо пам’ятати: коли ви бачите i++, майже завжди це означає звичайний лічильник і звичайну ітерацію.

Приклад зі зменшенням:

package main

import "fmt"

func main() {
	for i := 3; i > 0; i-- {
		fmt.Println(i) // 3 2 1
	}
}

Тут умова i > 0 і крок i-- узгоджені: ми рухаємося вниз до нуля, і цикл гарантовано зупиниться.

Порядок виконання: initcondbodypost

Поки ви не навчитеся прокручувати цикл у голові, будь-який for сприйматиметься як магія. Добра новина: магії немає — є чіткий порядок.

Спочатку виконується init — рівно один раз. Потім перевіряється cond. Якщо cond == true, виконується тіло body. Далі спрацьовує post. Після цього цикл повертається до перевірки cond. І так по колу, доки умова не стане хибною.

Ось схема, яку корисно тримати в голові, наче карту метро:

flowchart TD
    A[init: один раз] --> B{cond?}
    B -- true --> C[тіло]
    C --> D[post: крок]
    D --> B
    B -- false --> E[після циклу]

Якщо схеми вам не до вподоби, можна запамʼятати це як таблицю:

Елемент Коли виконується Типовий вміст
init
один раз, на початку
i := 0
cond
перед кожною ітерацією
i < n
body
якщо
cond == true
корисна дія
post
після тіла
i++

3. Корисні звички та важливі нюанси

Звичка: точно розуміти, які значення набуде i

Для новачка це справжня суперсила: вміти відповісти на запитання «які значення набуде лічильник». Не приблизно, а точно. Інакше ви постійно опинятиметеся в ситуації «я очікував 5 повторів, а отримав 6».

Розберімо це на прикладі:

for i := 0; i < 5; i++ { ... }

Лічильник стартує з 0. Умова: i < 5. Отже, i буде: 0, 1, 2, 3, 4. Пʼятірка вже не потрапить, бо при i == 5 умова стане хибною.

А тут:

for i := 1; i <= 5; i++ { ... }

Лічильник: 1, 2, 3, 4, 5. Тут 5 входить, бо це умова «менше або дорівнює».

Це здається дрібницею, але на практиці саме в цьому половина успіху в циклах.

Чому i зазвичай оголошують прямо в for

Коли ви пишете for i := 0; ..., ви наче кажете: «Лічильник — службова змінна, нехай живе лише тут». Це допомагає не захаращувати main зайвими змінними, які потім випадково використовують не за призначенням.

Так код стає самодокументованим: ви відкриваєте файл, бачите цикл і одразу розумієте, де зʼявився i та як він змінюється. Жодних «а хто такий i і чому він раптом дорівнює 17» — хоча, звісно, інколи життя все одно підкидає сюрпризи.

init, cond, post — частини одного механізму

Дуже часта помилка початківця — сприймати init, cond, post як три формальні частини: мовляв, «усі так пишуть». Насправді це три деталі одного механізму керування.

init відповідає за стартовий стан. cond — за правило «коли продовжуємо». post — за рух стану до завершення циклу. Якщо хоча б одна частина обрана невдало, цикл або виконає не ту кількість ітерацій, або взагалі зависне.

Суперпростий тест на здоровий глузд такий: спробуйте відповісти на два запитання. Яка змінна змінюється на кожній ітерації? І як вона наближає нас до моменту, коли cond стане хибним? Якщо на ці запитання немає відповіді — цикл підозрілий.

4. Приклад: TaskBox

Щоб вивчати for не абстрактно, почнімо збирати невеликий застосунок, який поступово зростатиме разом із курсом. Назвемо його TaskBox: поки що це зовсім простий CLI, який допомагає користувачеві вводити завдання й бачити їх із нумерацією.

Сьогодні в нас ще немає масивів і слайсів, тому тримати список завдань ми поки що не вміємо. Але вже можемо зробити корисну річ: повторити введення N разів і красиво пронумерувати те, що ввели. Це вже схоже на справжній застосунок, а не на «виведи hello 10 разів».

Читаємо N і друкуємо номери завдань

Цей невеликий приклад допомагає відчути, скільки разів повториться цикл і як виглядає нумерація.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	for i := 1; i <= n; i++ {
		fmt.Println("Завдання №", i) // при n=3: Завдання № 1 / 2 / 3
	}
}

Тут i — це зручна для людини нумерація. Ми спеціально обрали старт із 1, щоб вивід був зрозумілим і природним.

Повторюємо введення N разів і друкуємо те, що ввели

Тепер додамо назву завдання. Нагадаю: fmt.Scan читає токени, тож для простоти нехай завдання буде одним словом (наприклад, купити_молоко). Це нормально для навчального кроку.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	for i := 1; i <= n; i++ {
		var title string
		fmt.Scan(&title)
		fmt.Println(i, "-", title) // приклад: 1 - купити_молоко
	}
}

Це вже практичний патерн: цикл керує кількістю повторів, а всередині ми робимо одну й ту саму операцію — читаємо і друкуємо.

5. Ще один приклад: таблиця квадратів

Іноді корисно побачити, що цикл — це не лише введення й виведення, а й обчислення. Тут ми друкуємо таблицю квадратів від 1 до N. Жодних нових конструкцій — лише for і множення.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	for i := 1; i <= n; i++ {
		fmt.Println(i, "->", i*i) // при n=3: 1->1, 2->4, 3->9
	}
}

Ця вправа особливо добра тим, що результат можна швидко перевірити очима. Якщо на місці 3 раптом зʼявилося 16, значить, ви десь збилися з лічильника.

6. Типові помилки під час написання класичного for

Помилка № 1: переплутати < і <= та отримати зайву або відсутню ітерацію.
Це не «неуважність», а справді найчастіша історія. Мозок читає i < 5 і думає: «Ну, до пʼяти». А компʼютер читає буквально: «строго менше за пʼять». Допомагає звичка промовляти: «Яким буде останнє значення i?».

Помилка № 2: неправильний старт лічильника (наприклад, почали з 0, а виводите «спроба № 0»).
Технічно код правильний, але користувач дивитиметься на «спроба № 0» з підозрою — як на чек із магазину з «-1 товаром». Для людської нумерації частіше обирають старт із 1. Для індексації, коли вона зʼявиться пізніше, часто обирають старт із 0 — але саме пізніше, а не всюди й завжди.

Помилка № 3: крок не узгоджений з умовою.
Якщо умова i < n, а крок i--, то ви ніколи не наблизитеся до i >= n. Це як їхати на машині до фінішу, але щоразу після повороту відʼїжджати в протилежний бік. Компілятор не рятує від логічних помилок — він лише перевіряє синтаксис.

Помилка № 4: спроба «скласти все одразу» в один рядок циклу.
Іноді новачки намагаються зробити «круто»: складна ініціалізація, хитра умова, крок із кількома змінними — і все це одразу. У підсумку цикл стає загадкою навіть для автора через пʼять хвилин. На старті краще тримати init простим (i := 0), cond зрозумілим (i < n) і post очевидним (i++). Краса прийде, коли мозок перестане витрачати сили на базову механіку.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ