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.");
1 2 3 4 5 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:

koloi na začátkui <= 5?vypíšei po i++
1.1true12
2.2true23
3.3true34
4.4true45
5.5true56
—6falsekonec 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:

  1. začátek — int i = 1; před cyklem,
  2. podmínku — i <= 5, dokud platí, jede se dál,
  3. krok — i++ v těle, který podmínku jednou shodí.
Past č. 1: nekonečný cyklus Zapomeňte na 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.
Past č. 2: středník za 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:

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.

A když napíšu „ahoj“? 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:

částv příkladukdy se provede
začátekint i = 1jednou, úplně na začátku
podmínkai <= 5před každým kolem; když neplatí, konec
kroki++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);
}
Proč 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.
Past č. 3: o jedničku vedle
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?

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).

Past č. 4: akumulátor uvnitř 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.
Výpis patří za cyklus Když dáte 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);
}
Opatrně s nimi 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
Past č. 5: horní mez se nepočítá 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.

Úkol 1 · zahřívací

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?

Úkol 2

Násobilka

Načtěte číslo a vypište jeho násobilku od 1 do 10 v tomhle tvaru:

7 × 1 = 7 7 × 2 = 14 … 7 × 10 = 70

Test: co vypíše pro 0? A pro −3?

Úkol 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.

Úkol 4

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ů?

Úkol 5 · pro rychlé

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

Pravidlo pro AI platí pořád: klidně se zeptejte, proč vám cyklus neskončí, nebo si nechte vysvětlit rozdíl mezi 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.