JavaRush /Курси /Kotlin SELF /Знайомство з циклом while

Знайомство з циклом while

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

1. Вступ

Уявіть, що ви пишете програму, яка просить користувача ввести вік. Користувач вводить "abc". Ви відповідаєте "некоректно". І… що далі? Якщо у вас є лише if, програма просто дійде до кінця й завершиться, залишивши користувача з відчуттям: «ну й гаразд тоді». Цикли потрібні саме для цього — навчити програму повторювати дію, доки не буде досягнуто потрібного результату. Або, якщо сказати інакше, доки умова істинна.

У реальному житті цикли трапляються всюди: повторне введення, підрахунки «від 1 до N», накопичення суми, поступове наближення до цілі. І так, цикли — головний інструмент, за допомогою якого можна випадково влаштувати нескінченність (програмісти інколи теж люблять вічність, але компілятор — не завжди).

2. while: синтаксис і ментальна модель

Цикл while читається дуже по-людськи: «поки умова істинна — роби». У Kotlin він виглядає так:

while (condition) {
    // тіло циклу
}
Базовий синтаксис циклу while у Kotlin

Головна особливість: умова перевіряється до входу в тіло. Це означає, що якщо умова одразу false, тіло не виконається жодного разу. І це не «помилка», а корисна поведінка. Саме тому while ідеально підходить для ситуацій на кшталт «виконуємо, доки має сенс».

while — це як охоронець на вході до клубу: «Є квиток?» (умова). Якщо квитка немає, усередину не впустять узагалі — ні на одну ітерацію.

Невелика блок-схема, щоб побачити рух програми буквально очима:

flowchart TD
    A[Старт] --> B{condition?}
    B -- ні --> E[Вихід із циклу]
    B -- так --> C[Тіло циклу]
    C --> D[Кінець ітерації]
    D --> B

І так: Kotlin — це Kotlin, але while тут цілком класичний — «як усюди», без сюрпризів. Навіть в офіційних прикладах імперативного стилю (де використовують var і цикл) while виглядає саме так.

3. Шаблони while

Лічильник: три обовʼязкові частини

Коли новачки бачать цикл, перша думка часто така: «О, повторення! Зараз я повторю щось… і воно само зупиниться». Не зупиниться. Циклу потрібно допомогти.

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

  1. початкове значення (до циклу),
  2. умова продовження (у while (...)),
  3. зміна змінної, щоб умова колись стала хибною (усередині тіла).

Найпростіший приклад — друкуємо числа від 1 до 5:

fun main() {
    var i = 1

    while (i <= 5) {
        println("i = $i") // i = 1, потім 2, 3, 4, 5
        i = i + 1
    }
}

Зверніть увагу: змінна i оголошена як var, бо ми її змінюємо. Якби це було val, компілятор справедливо сказав би: «ви ж обіцяли не змінювати».

А тепер приклад із межею, де тіло може не виконатися жодного разу. Це важлива «перевірка на чесність» для вашого розуміння:

fun main() {
    var i = 10

    while (i <= 5) {
        println("Це не надрукується")
        i = i + 1
    }

    println("Цикл не запускався жодного разу") // Цикл не запускався жодного разу
}

Умова i <= 5 одразу false, тому всередину циклу ми не потрапили. Іноді це саме те, що потрібно. Наприклад: «повторювати, доки є помилки», а помилок може не бути вже на старті.

Накопичення: сума і максимум

Цикл while корисний не лише для друку «кроків», а й для накопичення результату. Типовий сценарій: «є кілька чисел — потрібно порахувати суму». Поки у нас немає масивів і списків, ми тренуємося на фіксованій кількості значень або на варіанті «вводимо N разів».

Почнемо із зовсім малого: сума від 1 до 3.

fun main() {
    var sum = 0
    var i = 1

    while (i <= 3) {
        sum = sum + i
        i = i + 1
    }

    println("sum = $sum") // sum = 6
}

Тут дві ролі змінних дуже чіткі: i — «крок», sum — «накопичувач». Коли ви змішуєте ролі (наприклад, використовуєте одну змінну і як лічильник, і як суму), код стає схожим на жарт, який смішний лише автору.

Ще один корисний шаблон — знаходити максимум серед кількох введених значень. Так, пізніше ми зробимо це набагато зручніше, але зараз нам важлива логіка «порівнюємо й оновлюємо».

Припустімо, ми хочемо обробити 3 числа (введення вважаємо коректним, щоб не відволікатися від теми):

fun main() {
    var max: Int? = null
    var count = 0

    while (count < 3) {
        print("Введіть число: ")
        val n = readln().trim().toInt()

        if (max == null || n > max) max = n
        count = count + 1
    }

    println("max = $max") // наприклад: max = 42
}

Тут max зроблено nullable (Int?), бо «максимуму» до першого введення ще немає. Це хороший приклад того, що nullable-типи — не лише для «страшних тем про null», а інколи просто чесний спосіб сказати: «значення зʼявиться пізніше».

Нескінченний цикл: чому він виникає і як його виявити

Цикл while в реальному житті: поки Барні в барі — Мо викидає його. Але Барні завжди повертається… Умова ніколи не змінюється = нескінченний цикл!

Час поговорити про головну розвагу початківця програміста: «я запустив програму, а вона зависла». Насправді вона часто не зависла — вона просто знайшла сенс життя й тепер нескінченно повторює одне й те саме.

Причина нескінченного циклу майже завжди одна: умова не стає false. Або ви забули змінити змінну, або змінюєте не ту, або змінюєте «не туди».

Найкласичніша помилка:

fun main() {
    var i = 1
    while (i <= 5) {
        println("i = $i")
        // забули i = i + 1
    }
}

Тут i завжди 1, умова завжди істинна, і програма друкуватиме "i = 1" доти, доки ви (або ваша операційна система) не скажете: «усе, досить».

Ще більш підступна версія — ви оновлюєте змінну, але так, що умова ніколи не стане хибною:

fun main() {
    var i = 1
    while (i <= 5) {
        println("i = $i")
        i = i - 1
    }
}

i іде в 0, -1, -2… і умова i <= 5 залишається істинною назавжди.

Практичне правило «анти-нескінченність» звучить нудно, але працює залізно: після написання while очима знайдіть у тілі рядок, який впливає на умову. Якщо не знайшли — у вас майже напевно вічний двигун.

4. Повторне введення і перевірка

Лічильники — це добре, але одна з найжиттєвіших причин полюбити while — надійне введення. Користувач може ввести пробіли, порожній рядок, «сорок два» літерами й навіть "42.0" (а ви хотіли Int). І якщо ви використовуєте toInt(), програма може впасти. Тому для сценарію «повторювати введення до коректного значення» ми використовуємо звʼязку trim() + toIntOrNull().

readln() читає рядок повністю, а toIntOrNull() повертає або число, або null, якщо рядок — не число. Це ідеально лягає на while.

Приклад: просимо вік (ціле число від 0 до 130) і повторюємо, доки не отримаємо нормальний результат.

fun main() {
    var age: Int? = null

    while (age == null) {
        print("Введіть вік (0..130): ")
        val raw = readln().trim()
        val parsed = raw.toIntOrNull()

        if (parsed != null && parsed in 0..130) {
            age = parsed
        } else {
            println("Некоректне введення. Спробуйте ще раз.")
        }
    }

    println("Вік прийнято: $age") // наприклад: Вік прийнято: 25
}

Чому це зручно:

  1. Ми не «падаємо» на поганому введенні — бо не використовуємо toInt() напряму.
  2. Умова циклу дуже читабельна: «доки вік не отримано».
  3. Усередині циклу чітко видно, що саме робить введення коректним: число має розпарситися й потрапити в діапазон.

Зверніть увагу на важливу дрібницю: перевірка parsed in 0..130 можлива лише якщо parsed != null. Тому ми пишемо parsed != null && … Це продовження тієї самої логіки, яку ви вже застосовували в умовах із && і ||.

5. Міні‑застосунок: BudgetBuddy v0

Зараз зберемо невеликий цілісний сценарій, який виглядатиме як «справжня програма», а не набір розрізнених прикладів. Нехай це буде BudgetBuddy v0: мініпомічник, який просить ввести три витрати й рахує суму. Головний фокус — цикл while і стійкість до неправильного введення без break/continue (їх ми ще не проходили).

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

fun main() {
    var sum = 0
    var entered = 0

    while (entered < 3) {
        print("Введіть витрату №${entered + 1} (ціле число >= 0): ")
        val raw = readln().trim()
        val value = raw.toIntOrNull()

        if (value != null && value >= 0) {
            sum = sum + value
            entered = entered + 1
        } else {
            println("Некоректно. Потрібне ціле число >= 0.")
        }
    }

    println("Сума 3 витрат = $sum") // наприклад: Сума 3 витрат = 1200
}

Зверніть увагу, як акуратно розподілилися ролі:

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

І ще одна деталь про читабельність: "№${entered + 1}" — це рядковий шаблон, який ви вже знаєте. Він робить підказку дружньою: користувач розуміє, що вводить «витрату №2», а не просто якісь абстрактні числа в порожнечу.

6. Типові помилки під час роботи з while

Помилка № 1: забули змінити змінну, від якої залежить умова.
Найчастіша причина нескінченного циклу — змінна в умові (i, entered, age) лишається незмінною. Корисна звичка: одразу після написання while (...) очима знайти в тілі рядок, який зрештою робить умову хибною. Якщо такого рядка немає, цикл, найімовірніше, буде вічним.

Помилка № 2: оновлюєте «не туди», і умова ніколи не стане хибною.
Іноді оновлення є, але логіка неправильна. Наприклад, у циклі while (i <= 5) ви робите i = i - 1. Формально програма «щось змінює», але за змістом вона рухається в бік, де умова лишається істинною нескінченно. Коли помічаєте, що значення «тікає» від межі, зупиніться й перевірте знак зміни.

Помилка № 3: змішали ролі змінних, і цикл перестав бути зрозумілим.
Якщо однією змінною ви і рахуєте кроки, і зберігаєте суму, і ще намагаєтеся тримати «останнє введення», код стає важко читати й легко зламати. Хороший стиль для новачка — «одна змінна = одна роль»: окремо лічильник, окремо накопичувач, окремо розпарсене значення.

Помилка № 4: використовуєте toInt() у циклі введення й ловите падіння програми.
toInt() хороший, коли ви впевнені у форматі введення. Але в задачах «введення від користувача» впевненості зазвичай немає. Тому для повторного введення краще використовувати toIntOrNull() і перевірку на null, як ми зробили вище. Так програма не падає й може ввічливо попросити повторити введення.

Помилка № 5: порівнюєте або перевіряєте діапазон для значення, яке може бути null.
Типова ситуація: val x = raw.toIntOrNull() — і далі спроба зробити if (x >= 0). Компілятор Kotlin не дасть так написати (і матиме рацію), бо x може бути null. Правильний порядок думок такий: спочатку «не null?», потім «чи підходить діапазон?». Це особливо важливо в циклах, бо помилка в перевірці часто перетворюється на нескінченне повторення або на неможливість прийняти коректне введення.

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