CodeGym /Kursy /Kotlin SELF /Pierwsze spotkanie z petla while

Pierwsze spotkanie z petla while

Kotlin SELF
Poziom 4, Lekcja 1
Dostępny

1. Wstep

Wyobraz sobie, ze piszesz program, ktory prosi uzytkownika o podanie wieku. Uzytkownik wpisuje "abc". Ty mówisz "niepoprawnie". I… co dalej? Jesli masz tylko if, program po prostu dochodzi do konca i sie konczy, zostawiajac uzytkownika z poczuciem „no to trudno”. Petle sa potrzebne dokladnie do tego: nauczyc program powtarzac dzialanie, dopoki nie zostanie osiagniety oczekiwany rezultat — albo dopoki warunek jest prawdziwy.

W prawdziwym zyciu petle spotyka sie wszedzie: ponowne wpisywanie danych, zliczanie „od 1 do N”, sumowanie, stopniowe zblizanie sie do celu. I tak, petle to glowny instrument, dzieki ktoremu latwo przypadkowo zorganizowac nieskonczonosc (programisci czasem tez lubia wiecznosc, ale kompilator — nie zawsze).

2. while: skladnia i model mentalny

Petla while czyta sie bardzo „po ludzku”: „dopoki warunek jest prawdziwy — rob”. W Kotlinie wyglada tak:

while (condition) {
    // ciało petli
}
Podstawowa skladnia petli while w Kotlinie

Najwazniejsza cecha: warunek jest sprawdzany przed wejsciem do ciala. To znaczy, ze jesli warunek od razu jest false, ciało nie wykona sie ani razu — i to nie „blad”, tylko przydatne zachowanie. Wlasnie dlatego while idealnie pasuje do sytuacji typu „wykonujemy, dopoki ma to sens”.

while jest jak ochroniarz przy wejsciu do klubu: „Masz bilet?” (warunek). Jesli biletu nie ma, nie wpusci w ogole — nawet na jedna iteracje.

Mala blok-schemat, zeby zobaczyc ruch programu „oczami”:

flowchart TD
    A[Start] --> B{condition?}
    B -- no --> E[Wyjscie z petli]
    B -- yes --> C[Cialo petli]
    C --> D[Koniec iteracji]
    D --> B

I tak: Kotlin to Kotlin, ale while jest tu absolutnie klasyczny, „jak wszedzie”, bez niespodzianek. Nawet w oficjalnych przykladach stylu imperatywnego (gdzie uzywa sie var i petli) while wyglada dokladnie tak.

3. Wzorce dla while

Licznik: trzy obowiazkowe czesci

Gdy poczatkujacy widza petle, pierwsza mysl jest czesto taka: „O, powtarzanie! Teraz cos powtorze… i to samo sie zatrzyma”. Nie zatrzyma sie. Petli trzeba pomoc.

Klasyczna petla-licznik ma trzy czesci, i prawie zawsze widac je bezposrednio w kodzie:

  1. wartosc poczatkowa (przed petla),
  2. warunek kontynuacji (w while (...)),
  3. zmiana zmiennej tak, aby warunek kiedys stal sie falszywy (wewnatrz ciala).

Najprostszy przyklad — wypisujemy liczby od 1 do 5:

fun main() {
    var i = 1

    while (i <= 5) {
        println("i = $i") // i = 1, potem 2, 3, 4, 5
        i = i + 1
    }
}

Zwróc uwage: zmienna i jest zadeklarowana jako var, bo ja zmieniamy. Gdyby to bylo val, kompilator slusznie powiedzialby: „przeciez obiecalas/obiecales nie zmieniac”.

Teraz przyklad z granica, gdzie ciało moze nie wykonac sie ani razu. To bardzo wazny „test uczciwosci” dla twojego zrozumienia:

fun main() {
    var i = 10

    while (i <= 5) {
        println("To sie nie wydrukuje")
        i = i + 1
    }

    println("Petla nie uruchomila sie ani razu") // Petla nie uruchomila sie ani razu
}

Warunek i <= 5 od razu jest false, wiec do srodka petli nie weszlismy. Czasem to jest dokladnie to, czego trzeba: na przyklad „powtarzaj, dopoki sa bledy”, a bledow moze nie byc juz na starcie.

Akumulacja: suma i maksimum

Petla while jest przydatna nie tylko do wypisywania „krokow”, ale i do akumulowania wyniku. Typowy scenariusz: „jest kilka liczb, trzeba policzyc sume”. Dopoki nie mamy tablic i list, cwiczymy na stalej liczbie wartosci albo na „wprowadzamy N razy”.

Zacznijmy od czegos malutkiego: suma od 1 do 3.

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

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

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

Tutaj role zmiennych sa bardzo czytelne: i — „krok”, sum — „akumulator”. Gdy mieszasz role (np. uzywasz jednej zmiennej i jako licznika, i jako sumy), kod zaczyna przypominac zart, ktory jest smieszny tylko dla autora.

Jeszcze jeden przydatny wzorzec — znajdowanie maksimum sposrod kilku wprowadzonych wartosci. Tak, pozniej zrobimy to znacznie wygodniej, ale teraz liczy sie logika „porownaj i zaktualizuj”.

Zalozmy, ze chcemy przetworzyc 3 liczby (wejscie uznajemy za poprawne, zeby nie mieszac w temacie):

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

    while (count < 3) {
        print("Wpisz liczbe: ")
        val n = readln().trim().toInt()

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

    println("max = $max") // np.: max = 42
}

Tutaj max jest nullable (Int?), bo „maksimum” jeszcze nie istnieje przed pierwszym wprowadzeniem. To dobry przyklad, ze typy nullable sa nie tylko do „strasznych tematow o null”, ale czasem po prostu uczciwym sposobem powiedzenia: „wartosc pojawi sie pozniej”.

Nieskonczona petla: dlaczego powstaje i jak ja wykryc

Petla while w prawdziwym zyciu: Dopoki Barney jest w barze — Moe go wyrzuca. Ale Barney zawsze wraca... Warunek nigdy sie nie zmienia = nieskonczona petla!

Czas porozmawiac o glownej rozrywce poczatkujacego programisty: „uruchomilem program i sie zawiesil”. W rzeczywistosci czesto sie nie zawiesil — po prostu znalazl sens zycia i teraz nieskonczenie powtarza to samo.

Przyczyna nieskonczonej petli prawie zawsze jest jedna: warunek nie staje sie false. Albo zapomniales(-as) zmienic zmienna, albo zmieniasz nie te, albo zmieniasz „w zla strone”.

Najbardziej klasyczny blad:

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

Tutaj i zawsze wynosi 1, warunek jest zawsze prawdziwy i program bedzie wypisywal "i = 1" dopoty, dopoki ty (albo twoj system operacyjny) nie powiesz: „dosc, konczymy”.

Jeszcze bardziej podstepna wersja — aktualizujesz zmienna, ale w taki sposob, ze warunek nigdy nie stanie sie falszywy:

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

i schodzi do 0, -1, -2… a warunek i <= 5 pozostaje prawdziwy na zawsze.

Praktyczna zasada „anty-nieskonczonosc” brzmi nudno, ale dziala niezawodnie: po napisaniu while znajdz wzrokiem w ciele linie, ktora wplywa na warunek. Jesli jej nie ma — niemal na pewno masz perpetuum mobile.

4. Ponowne wprowadzanie danych i walidacja

Licznik to fajna rzecz, ale jedna z najbardziej zyciowych przyczyn, by polubic while, to odporny input. Uzytkownik moze wpisac spacje, pusta linie, „czterdziesci dwa” literami, a nawet "42.0" (a ty chciales(-as) Int). A jesli uzyjesz toInt(), program moze sie wysypac. Dlatego w scenariuszu „powtarzaj wprowadzanie az do poprawnej wartosci” uzywamy polaczenia trim() + toIntOrNull().

readln() czyta cala linie, a toIntOrNull() zwraca albo liczbe, albo null, jesli tekst nie jest liczba. To idealnie pasuje do while.

Przyklad: prosimy o wiek (liczba calkowita od 0 do 130) i powtarzamy, dopoki nie dostaniemy sensownego wyniku.

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

    while (age == null) {
        print("Podaj wiek (0..130): ")
        val raw = readln().trim()
        val parsed = raw.toIntOrNull()

        if (parsed != null && parsed in 0..130) {
            age = parsed
        } else {
            println("Niepoprawne dane. Sprobuj jeszcze raz.")
        }
    }

    println("Wiek przyjety: $age") // np.: Wiek przyjety: 25
}

Dlaczego to jest wygodne:

  1. Nie „wywalamy sie” na zlym wejsciu — bo nie uzywamy toInt() bezposrednio.
  2. Warunek petli jest bardzo czytelny: „dopoki wiek nie zostal uzyskany”.
  3. W srodku petli wyraznie widac, co sprawia, ze dane sa poprawne: liczba musi sie sparsowac i musi byc w zakresie.

Zauwaz wazny szczegol: sprawdzenie parsed in 0..130 jest mozliwe tylko wtedy, gdy parsed != null. Dlatego piszemy parsed != null && ... To kontynuacja tej samej logiki, ktora juz stosowales(-as) w warunkach z && i ||.

5. Miniaplikacja: BudgetBuddy v0

Teraz zlozymy niewielki, spojny scenariusz, ktory bedzie wygladal jak „prawdziwy program”, a nie zbior oderwanych przykladow. Niech to bedzie BudgetBuddy v0: mini-pomocnik, ktory prosi o wpisanie trzech wydatkow i liczy sume. Glowny fokus — petla while i odpornosc na bledne dane bez break/continue (tego jeszcze nie omawialismy).

Pomysl jest taki: potrzebujemy dokladnie 3 poprawnych liczb. Jesli uzytkownik wpisze bzdury — nie ruszamy licznika i prosimy ponownie. To bardzo wazny trik: licznik zwieksza sie tylko przy sukcesie.

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

    while (entered < 3) {
        print("Podaj wydatek nr ${entered + 1} (liczba calkowita >= 0): ")
        val raw = readln().trim()
        val value = raw.toIntOrNull()

        if (value != null && value >= 0) {
            sum = sum + value
            entered = entered + 1
        } else {
            println("Niepoprawnie. Potrzebna jest liczba calkowita >= 0.")
        }
    }

    println("Suma 3 wydatkow = $sum") // np.: Suma 3 wydatkow = 1200
}

Zwróc uwage, jak starannie rozdzielily sie role:

entered odpowiada tylko za to, ile poprawnych wydatkow przyjelismy. Jesli wejscie jest bledne, entered sie nie zmienia i zostajemy na tym samym „numerze wydatku”. To wlasnie jest „ponowne wprowadzanie” w prawdziwym zyciu, bez duplikowania kodu.

I jeszcze jeden szczegol o czytelnosci: "№${entered + 1}" to szablon lancucha, ktory juz znasz. Sprawia, ze podpowiedz jest przyjazna: uzytkownik widzi, ze wpisuje „wydatek nr 2”, a nie jakies abstrakcyjne liczby w proznie.

6. Typowe bledy podczas pracy z while

Blad nr 1: zapomniales(-as) zmienic zmienna, od ktorej zalezy warunek.
Najczestsza przyczyna nieskonczonej petli — zmienna w warunku (i, entered, age) pozostaje niezmienna. Przydatny nawyk: zaraz po napisaniu while (...) znalezc wzrokiem w ciele linie, ktora sprawi, ze warunek kiedys stanie sie falszywy. Jesli takiej linii nie ma — petla najpewniej bedzie wieczna.

Blad nr 2: aktualizujesz „w zla strone” i warunek nigdy nie stanie sie falszywy.
Czasem aktualizacja jest, ale logika jest bledna: np. w petli while (i <= 5) robisz i = i - 1. Formalnie program „cos zmienia”, ale sensownie ucieka w strone, w ktorej warunek pozostaje prawdziwy w nieskonczonosc. Gdy widzisz, ze wartosc „ucieka” od granicy — zatrzymaj sie i sprawdz znak zmiany.

Blad nr 3: pomieszales(-as) role zmiennych i petla przestala byc zrozumiala.
Jesli jedna zmienna liczysz kroki, przechowujesz sume i jeszcze probujesz trzymac „ostatnie wejscie”, kod staje sie trudny do czytania i latwo go zepsuc. Dobry styl dla poczatkujacego — „jedna zmienna = jedna rola”: osobno licznik, osobno akumulator, osobno sparsowana wartosc.

Blad nr 4: uzywasz toInt() w petli wejscia i lapiesz wywalenie programu.
toInt() jest dobre, gdy masz pewnosc co do formatu danych. Ale w zadaniach „wejscie od uzytkownika” tej pewnosci zwykle nie ma. Dlatego dla ponownego wprowadzania lepiej uzywac toIntOrNull() i sprawdzenia na null, jak zrobilismy wyzej: program sie nie wysypie i moze grzecznie poprosic o ponowienie.

Blad nr 5: porownujesz albo sprawdzasz zakres dla wartosci, ktora moze byc null.
Typowa sytuacja: val x = raw.toIntOrNull() i dalej proba zrobienia if (x >= 0). Kompilator Kotlin na to nie pozwoli (i bedzie mial racje), bo x moze byc null. Poprawna kolejnosc myslenia — najpierw „nie null?”, potem „czy pasuje do zakresu?”. To szczegolnie wazne w petlach, bo blad w sprawdzaniu czesto zamienia sie w nieskonczone powtarzanie albo w niemoznosc przyjecia poprawnego wejscia.

1
Zadanie
Kotlin SELF, poziom 4, lekcja 1
Niedostępne
Numery przepustek
Numery przepustek
1
Zadanie
Kotlin SELF, poziom 4, lekcja 1
Niedostępne
Suma nagród
Suma nagród
1
Zadanie
Kotlin SELF, poziom 4, lekcja 1
Niedostępne
Rejestracja imienia
Rejestracja imienia
1
Zadanie
Kotlin SELF, poziom 4, lekcja 1
Niedostępne
Menu aplikacji
Menu aplikacji
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION