Lekce 4 — Cykly
Základy programování v C# · čtvrtá dvouhodinovka
Minule se program naučil rozhodnout. Pořád ale platí, že každý řádek proběhne nejvýš jednou. Chcete vypsat čísla 1 až 10? Deset řádků Console.WriteLine. Do tisíce? Tisíc řádků. Chcete se ptát na heslo, dokud ho uživatel nezadá správně? To nejde vůbec — nevíte předem, kolikrát se splete.
Dnes se program naučí opakovat. Konstrukci, která opakuje kus kódu, se říká cyklus (anglicky loop, uslyšíte i „smyčka“).
1. while — opakuj, dokud platí
int i = 1;
while (i <= 5)
{
Console.WriteLine(i);
i++;
}
Console.WriteLine("Hotovo.");
Vypadá to skoro jako if a čte se podobně: „Dokud je i nejvýš 5, proveď tělo.“ Rozdíl je v tom, co se stane po těle. if jde dál. while se vrátí nahoru a zeptá se znovu. Teprve když podmínka neplatí, cyklus skončí a program pokračuje pod ním.
Projděte si to krok po kroku. Tomuhle se říká krokování a u cyklů je to nejlepší způsob, jak pochopit, co se děje:
| kolo | i na začátku | i <= 5? | vypíše | i po i++ |
|---|---|---|---|---|
| 1. | 1 | true | 1 | 2 |
| 2. | 2 | true | 2 | 3 |
| 3. | 3 | true | 3 | 4 |
| 4. | 4 | true | 4 | 5 |
| 5. | 5 | true | 5 | 6 |
| — | 6 | false | konec cyklu, pokračuje se „Hotovo.“ | |
Všimněte si posledního řádku: podmínka se vyhodnotí šestkrát, tělo proběhne pětkrát. A po cyklu je v i šestka, ne pětka.
Každý takový cyklus potřebuje tři věci:
- začátek —
int i = 1;před cyklem, - podmínku —
i <= 5, dokud platí, jede se dál, - krok —
i++v těle, který podmínku jednou shodí.
i++ a v i zůstane navždy jednička. Podmínka platí pořád, program sype jedničky donekonečna a nikdy neskončí. Nic se nerozbije — jen ho musíte zastavit: v okně konzole Ctrl+C, ve Visual Studiu červený čtvereček nebo Shift+F5. Každý programátor to udělal stokrát. Důležité je poznat, proč se to stalo: v těle cyklu chybí cokoliv, co by změnilo výsledek podmínky.
while
while (i <= 5); // ← tenhle středník je chyba
{
Console.WriteLine(i);
i++;
}
Znáte to z minula u if. Tady je to ještě zrádnější: středník je prázdné tělo cyklu, které se opakuje pořád dokola, i se nikdy nezmění — a program zamrzne bez jediného výpisu. Když se vám po spuštění nic neděje a nic nevypisuje, hledejte středník za while (...).
2. Dokud to uživatel nezadá správně
Počítání do pěti je hezké na vysvětlení, ale hlavní síla while je jinde: v situacích, kdy předem nevíte, kolikrát se to bude opakovat.
Console.Write("Heslo: ");
string heslo = Console.ReadLine();
while (heslo != "tajne123")
{
Console.WriteLine("Špatně, zkus to znovu.");
Console.Write("Heslo: ");
heslo = Console.ReadLine();
}
Console.WriteLine("Vítej!");
Uživatel může heslo trefit napoprvé (tělo neproběhne ani jednou) nebo napodesáté. Cyklu je to jedno. Tady není žádné počítadlo — krok, který jednou shodí podmínku, je heslo = Console.ReadLine();.
Jedna věc tu ale skřípe: řádky Console.Write("Heslo: ") a Console.ReadLine() jsou tam dvakrát. Jednou před cyklem, aby bylo co porovnat, a jednou v něm. Na to je další druh cyklu.
3. do … while — nejdřív udělej, pak se ptej
string heslo;
do
{
Console.Write("Heslo: ");
heslo = Console.ReadLine();
}
while (heslo != "tajne123");
Console.WriteLine("Vítej!");
Podmínka je až na konci, takže tělo proběhne vždycky aspoň jednou. To je přesně to, co u zadávání chceme: nejdřív se zeptat, pak zkontrolovat. Žádné zdvojené řádky.
Dvě věci, které tu vypadají divně a mají svůj důvod:
- Středník za
while (...)— tady patří. Je to jediné místo, kde zawhilestředník je, protože tím celá konstrukce končí. string heslo;nahoře, bez hodnoty — proměnná musí vzniknout předdo. Kdybyste ji vytvořili uvnitř složených závorek, podmínka dole by ji neviděla. Proměnná žije jen v těch{ }, ve kterých vznikla. K tomu se ještě vrátíme v pasti č. 4.
Stejný vzor použijete pokaždé, když chcete číslo z nějakého rozsahu:
int cislo;
do
{
Console.Write("Zadej číslo od 1 do 10: ");
cislo = int.Parse(Console.ReadLine());
}
while (cislo < 1 || cislo > 10);
Podmínka popisuje, kdy je vstup špatně — dokud je špatně, ptej se znovu. Tohle je častý zdroj zmatku: while vždycky říká, kdy pokračovat, ne kdy skončit.
int.Parse spadne s chybou FormatException a cyklus vás nezachrání — program skončí dřív, než se k podmínce dostane. Řešení existuje (int.TryParse), ale potřebuje věc, kterou jsme zatím nebrali. Do té doby platí: uživatel zadává čísla.
4. for — když víte kolikrát
Cyklus z kapitoly 1 — začátek, podmínka, krok — je tak častý, že pro něj existuje zkratka. Tohle dělá úplně totéž:
for (int i = 1; i <= 5; i++)
{
Console.WriteLine(i);
}
Všechny tři části jsou na jednom řádku v závorce, oddělené středníky:
| část | v příkladu | kdy se provede |
|---|---|---|
| začátek | int i = 1 | jednou, úplně na začátku |
| podmínka | i <= 5 | před každým kolem; když neplatí, konec |
| krok | i++ | po každém kole, po těle |
Výhoda není v tom, že je to kratší. Výhoda je, že na krok nemůžete zapomenout — má v závorce své místo a prázdné místo je vidět na první pohled.
Pozpátku a po dvou:
for (int i = 10; i >= 1; i--) // 10, 9, 8, … 1
{
Console.WriteLine(i);
}
for (int i = 0; i <= 20; i += 2) // 0, 2, 4, … 20
{
Console.WriteLine(i);
}
i, když jsme si říkali, že jména mají něco znamenat?
Počítadlo cyklu je výjimka. i (od index) je tak zažitá zvyklost, že ji každý programátor přečte okamžitě. Jakmile ale číslo něco znamená — řádek, pokus, měsíc — pojmenujte ho podle toho: radek, pokus, mesic.
for (int i = 0; i < 10; i++) // proběhne 10× (0 až 9)
for (int i = 0; i <= 10; i++) // proběhne 11× (0 až 10)
for (int i = 1; i < 10; i++) // proběhne 9× (1 až 9)
Tahle chyba má i vlastní jméno — off-by-one — protože ji dělá úplně každý, včetně profesionálů. Program se přeloží, běží a je špatně o jedno kolo. Obrana: u každého cyklu si v hlavě projděte první a poslední kolo. S jakým i začnu? S jakým naposledy proběhne tělo? Kolik je to dohromady?
Který cyklus kdy?
- Víte předem, kolikrát (10×,
n×, od 1 do 100)? →for. - Opakujete, dokud se něco nestane (správné heslo, uhodnuté číslo, „konec“)? →
while. - …a má to proběhnout aspoň jednou? →
do…while.
5. Střádání výsledku
Cyklus nemusí jen vypisovat. Často během něj něco sčítáte, počítáte nebo hledáte. Třeba součet čísel od 1 do n:
Console.Write("Do kolika sčítat? ");
int n = int.Parse(Console.ReadLine());
int soucet = 0; // PŘED cyklem: připravit
for (int i = 1; i <= n; i++)
{
soucet += i; // V cyklu: přidat
}
Console.WriteLine($"Součet je {soucet}."); // PO cyklu: vypsat
Ten vzor před — v — po si zapamatujte, budete ho psát pořád. Proměnné, která sbírá výsledek, se říká akumulátor (střádač). Stejně se počítá i počet (pocet++ místo soucet += i) nebo hledá největší hodnota (if v těle cyklu).
for (int i = 1; i <= n; i++)
{
int soucet = 0; // ← v každém kole znovu od nuly
soucet += i;
}
Console.WriteLine(soucet); // nepřeloží se
Dvě chyby najednou. Proměnná vytvořená uvnitř { } se v každém kole narodí znovu — takže se nikdy nic nenasčítá. A za uzavírací } už neexistuje, takže ji nejde vypsat. Pravidlo: co si má cyklus pamatovat mezi koly, musí vzniknout před ním.
Console.WriteLine se součtem do těla, vypíše se mezisoučet v každém kole — deset řádků místo jednoho. Někdy se to hodí na hledání chyby (uvidíte, jak hodnota roste). Pak to zase smažte.
6. break a continue
break cyklus okamžitě ukončí — program pokračuje pod ním. Hodí se, když se o konci rozhodujete uprostřed těla, ne v podmínce. Typický příklad je menu, které se točí, dokud uživatel neřekne dost:
while (true)
{
Console.Write("Příkaz (ahoj / cas / konec): ");
string prikaz = Console.ReadLine().Trim().ToLower();
if (prikaz == "konec")
{
break; // vyskoč z cyklu
}
if (prikaz == "ahoj")
{
Console.WriteLine("Ahoj!");
}
else if (prikaz == "cas")
{
Console.WriteLine(DateTime.Now.ToShortTimeString());
}
else
{
Console.WriteLine("Neznámý příkaz.");
}
}
Console.WriteLine("Nashle.");
while (true) je naschvál nekonečný cyklus — jediná cesta ven je break. Není to chyba, je to běžný vzor. Podívejte se na něj pořádně: tohle je kostra té textové hry, kterou budeme psát v druhém pololetí. Hra není nic jiného než cyklus „přečti příkaz, udělej něco, opakuj“.
continue přeskočí zbytek tohohle kola a jde rovnou na další. Třeba čísla od 1 do 20 kromě násobků tří:
for (int i = 1; i <= 20; i++)
{
if (i % 3 == 0)
{
continue; // tohle kolo přeskoč
}
Console.WriteLine(i);
}
break a continue jsou užitečné, ale každé z nich je skok, který v kódu není vidět na první pohled. Když jde totéž napsat obyčejnou podmínkou, napište podmínku. while (true) + break si nechte na menu a podobné situace, kde se o konci rozhoduje uprostřed.
7. Náhodné číslo
Na hry budete potřebovat náhodu:
int tajne = Random.Shared.Next(1, 101); // náhodné číslo 1 až 100
int kostka = Random.Shared.Next(1, 7); // 1 až 6
Next(1, 100) vrací čísla 1 až 99. Stovka nepadne nikdy. První číslo do výsledku patří, druhé už ne — proto je u kostky Next(1, 7). Je to stejná logika jako i < 10 v cyklu, a je za tím stejný důvod: počítač často počítá od nuly a Next(0, 10) pak dá přesně deset možností.
8. Cyklus v cyklu
Tělo cyklu může obsahovat cokoliv — i další cyklus:
for (int radek = 1; radek <= 3; radek++)
{
for (int sloupec = 1; sloupec <= 5; sloupec++)
{
Console.Write("* ");
}
Console.WriteLine();
}
Vnitřní cyklus proběhne celý pro každé kolo vnějšího: pět hvězdiček, odřádkování, pět hvězdiček, odřádkování… Dohromady 3 × 5 = 15 hvězdiček.
Dvě drobnosti, na kterých to stojí: Console.Write (bez Line) píše bez odřádkování, takže hvězdičky jsou vedle sebe. A Console.WriteLine() s prázdnými závorkami nevypíše nic, jen odřádkuje. Všimněte si taky, že počítadla mají jména radek a sloupec — u dvou cyklů v sobě by vás i a j mátly.
9. Úkoly
Od nejlehčího. U každého cyklu si před spuštěním řekněte, kolikrát proběhne — a pak to ověřte.
Odpočet
Vypište čísla od 10 do 1, každé na řádek, a na konci „Start!“. Napište to dvakrát: jednou přes for, jednou přes while. Který zápis se vám čte líp?
Násobilka
Načtěte číslo a vypište jeho násobilku od 1 do 10 v tomhle tvaru:
Test: co vypíše pro 0? A pro −3?
Průměr známek
Načítejte známky, dokud uživatel nezadá 0. Pak vypište, kolik známek zadal, jejich součet a průměr. Známky mimo 1–5 nepočítejte — vypište „Tohle není známka.“ a ptejte se dál.
Nápověda: vzor před — v — po, dva akumulátory. Na průměr si vzpomeňte, co dělá 7 / 2 a co s tím dělá (double).
Test: co se stane, když jako úplně první zadáte 0? Program nesmí spadnout ani vypsat nesmysl.
Hádej číslo
Počítač si vymyslí číslo od 1 do 100. Uživatel hádá; po každém tipu dostane „Víc.“ nebo „Míň.“, dokud netrefí. Pak vypište „Uhodl/a jsi na 7 pokusů.“ (se skutečným počtem).
Bonus: po uhodnutí se zeptejte „Hrát znovu? (ano/ne)“ a když uživatel řekne ano, začne nová hra s novým číslem. Odpověď ať funguje i jako Ano nebo ano — vzpomeňte na minulou lekci. Budete potřebovat cyklus v cyklu.
Otázka k zamyšlení: jakou strategií uhodnete jakékoliv číslo nejvýš na 7 pokusů?
Trojúhelník a FizzBuzz
a) Načtěte výšku a nakreslete trojúhelník z hvězdiček. Pro výšku 4:
Nápověda: kolik hvězdiček má řádek číslo 3? Jak se to dostane do podmínky vnitřního cyklu?
b) Vypište čísla od 1 do 100, ale místo násobků 3 napište „Fizz“, místo násobků 5 „Buzz“ a místo násobků 3 i 5 zároveň „FizzBuzz“. Tahle úloha se opravdu dává na pohovorech, protože hodně lidí ji poprvé napíše špatně. Zkuste přijít na to proč — a napište ji správně napoprvé.
Shrnutí — co byste měli umět říct vlastními slovy
- Čím se liší
whileodif. - Jaké tři věci potřebuje každý cyklus s počítadlem a co se stane, když jedna chybí.
- Kdy použít
while, kdydo…whilea kdyfor. - Kolikrát proběhne
for (int i = 0; i < 10; i++)a jakou hodnotu máiv posledním kole. - Proč musí akumulátor vzniknout před cyklem.
- Co dělá
breaka cocontinue. - Proč
Random.Shared.Next(1, 100)nikdy nevrátí 100.
while a do … while. Ale u kontroly dostanete otázky jako „kolikrát tohle proběhne?“, „ať hádání skončí po deseti neúspěšných pokusech“ nebo „ať to počítá pozpátku“ — a odpovídáte vy, ne chatbot. Pozor taky na to, že AI ráda místo obyčejného cyklu napíše Enumerable.Range, LINQ nebo rekurzi. Funguje to, ale nebrali jsme to — a co neumíte vysvětlit, neodevzdávejte.