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?», потом «подходит ли диапазон?». Это особенно важно в циклах, потому что ошибка в проверке часто превращается в бесконечное повторение или в невозможность принять корректный ввод.

1
Задача
Kotlin SELF, 4 уровень, 1 лекция
Недоступна
Номера пропусков
Номера пропусков
1
Задача
Kotlin SELF, 4 уровень, 1 лекция
Недоступна
Сумма наград
Сумма наград
1
Задача
Kotlin SELF, 4 уровень, 1 лекция
Недоступна
Регистрация имени
Регистрация имени
1
Задача
Kotlin SELF, 4 уровень, 1 лекция
Недоступна
Меню приложения
Меню приложения
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ