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
}
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:
- wartosc poczatkowa (przed petla),
- warunek kontynuacji (w while (...)),
- 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
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:
- Nie „wywalamy sie” na zlym wejsciu — bo nie uzywamy toInt() bezposrednio.
- Warunek petli jest bardzo czytelny: „dopoki wiek nie zostal uzyskany”.
- 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.
GO TO FULL VERSION