Níže uvedený text pochází z prvního vydání. Nad tímto textem se nachází aktuální stav po revizi směřující k druhému vydání.

Příkazy cyklu — aneb umění opakování se

O čem si budeme povídat?

Řekneme si, jak užívat cykly, abychom se vyhnuli opakovanému psaní příkazů. Ukážeme si různé typy cyklů a to, kdy je máme použít.

Na konci předchozí kapitoly jsme si vytiskli část tabulky násobků čísla 12. Ale stálo nás to mnoho psaní a pokud bychom potřebovali tabulku rozšířit, bylo by to velmi časově náročné. Naštěstí existuje lepší způsob a na něm si ukážeme opravdovou sílu, kterou nám programovací jazyky nabízejí.

Cyklus typu FOR

Nyní si ukážeme, jak v programovacím jazyku zapíšeme, že se něco má opakovat. Budeme dosazovat hodnotu proměnné a při každém opakování ji současně budeme zvyšovat. Zápis v jazyce Python vypadá takto:

>>> for i in range(1, 13):
...     print "%d x 12 = %d" % (i, i*12)
...

Poznámka 1: V příkazu range(1, 13) musíme použít 13, protože range() generuje čísla od dolní hranice včetně až po horní zadanou hranici vyjma. Na první pohled se vám to může zdát podivné, ale jsou pro to dobré důvody a časem si na to zvyknete.

Poznámka 2: Operátor for jazyka Python je ve skutečnosti operátorem, který bývá označován jako foreach (doslova pro každý). Následující posloupnost příkazů je provedena pro každý prvek kolekce. V našem případě je touto kolekcí seznam čísel generovaných funkcí range(). Můžete si to ověřit zapsáním příkazu print range(1, 13) na příkazový řádek interpretu jazyka Python. Uvidíte, co se vytiskne.

Poznámka 3: Řádek s příkazem print je odsazen více, než předcházející řádek s příkazem for. To je velmi důležité, protože tím překladači jazyka Python dáváme najevo, že chceme opakovat právě příkaz print. Nezáleží na tom, jak velké odsazení použijete, ale zvolenou hodnotu odsazení musíte dodržovat.

Poznámka 4: Pokud s překladačem jazyka Python pracujete v interaktivním režimu, spustí se program až po dvojím stisku klávesy Enter. Důvod spočívá v tom, že po prvním stisku překladač jazyka Python nepozná, zda chcete k posloupnosti opakovaných příkazů přidat další řádek, či nikoliv. Pokud stisknete klávesu Enter podruhé, Python předpokládá, že jste již dokončili vkládání příkazů a program spustí.

Takže jak vlastně uvedený program pracuje? Projdeme si jej krok po kroku.

Python nejdříve použije funkci range() pro vytvoření seznamu čísel od 1 do 12. Poté Python přiřadí proměnné i první hodnotu seznamu. Následuje provedení odsazeného kódu při použití hodnoty i = 1:

    print "%d x 12 = %d" % (1, 1*12)

Potom se Python vrátí zpět na řádek příkazem for a přiřadí i další hodnotu se zeznamu, tentokrát 2. Opět se provede odsazený kus kódu, tentokrát s hodnotou i = 2:

    print "%d x 12 = %d" % (2, 2*12)

Python bude odsazenou posloupnost příkazů opakovat až do doby, kdy byly proměnné i přiřazeny všechny hodnoty seznamu. V ten okamžik, po provedení těla cyklu s poslední hodnotou v proměnné i, se provádění přesune na další příkaz, který není odsazen. V našem příkladu žádné další příkazy nemáme, takže dojde k ukončení programu.

Stejný cyklus v jazyce BASIC:

FOR I = 1 to 12
    PRINT I, " x 12 = ", I*12
NEXT I

V tomto zápisu je mnohem lépe na první pohled vidět, co se děje. Nicméně verze v jazyce Python je mnohem pružnější, protože cyklus může probíhat pro řadu čísel, pro položky seznamu nebo pro jakýkoliv jiný typ kolekce (například pro řetězec).

Poznámka překladatele: Zápis příkladu v jazyce Python můžeme přiblížit podobě příkladu v jazyce BASIC tím, že nevyužijeme vlastností formátovacího řetězce:

>>> for i in range(1, 13):
...     print i, "x 12 =", i*12
...

Jednou z výhod použití formátovacího řetězce je ale například možnost předepsat, jak se má číslo naformátovat — tj. na kolik pozic se má tisknout. Vyzkoušejte:

>>> for i in range(1, 13):
...     print "%2d x 12 = %3d" % (i, i*12)
...

Zápis stejného příkladu v jazyce Tcl

Konstrukce for, která je běžná v mnoha pogramovacích jazycích, má v jazyce Tcl podobu známou z jazyka C. Vypadá následovně:

for {set i 1} {$i <= 12} {incr i} {
    puts [format "%d x 12 = %d" $i [expr $i*12]]
    }

Poznámka: Tato konstrukce má 3 části:

Tělo cyklu se provede pouze v případě, kdy je podmínka v testové části splněna. Každá z uvedených částí může obsahovat libovolný kód, ale výsledkem výrazu v testové části musí být boolovská hodnota (což v případě jazyka Tcl znamená nulovou nebo nenulovou hodnotu). Ačkoliv jsem tělo cyklu zapsal odsazené, učinil jsem tak pouze pro zvýšení srozumitelnosti zápisu. Tcl nevyžaduje odsazování bloků. Místo toho se k označování začátku a konce bloku používají složené závorky.

V jazyce Tcl existuje také konstrukce foreach, kterou lze použít pro seznamy.

Cyklus typu WHILE

Cykly typu FOR nepředstavují jediný možný typ konstrukce cyklu. A to je dobře, protože u cyklu FOR musíme vědět, nebo musíme být schopni předem vypočítat, počet prováděných iterací. Takže co máme dělat v případech, kdy chceme pokračovat v provádění určitého úkolu až do doby, kdy nastane určitá situace, ale když přitom nevíme, kdy k dané situaci dojde? Můžeme například chtít načítat a zpracovávat data ze souboru, ale předem nevíme, kolik datových položek soubor obsahuje. Chtěli bychom prostě pokračovat ve zpracování dat až do dosažení konce souboru. Lze k tomu sice použít i cyklus FOR, ale je to obtížné.

K řešení tohoto problému se hodí jiný typ cyklu — cyklus typu WHILE. V jazyce BASIC by zápis vypadal nějak takto:

J = 1
WHILE J <= 12 DO
    PRINT J, " x 12 = ", J*12
    J = J + 1
WEND

Tento kód produkuje stejný výstup, jako v předchozím případě, ale místo cyklu for jsme použili cyklus while. Povšimněte si, že za while zapisujeme výraz, jehož výsledkem je boolovská hodnota (true nebo false — pamatujete?). Pokud je výsledkem hodnota true (pravda), pak bude kód uvnitř cyklu proveden.

Jako alternativu si nyní ukážeme verzi v Tcl:

set j 1
while {j <= 12} {
    puts [format "%d x 12 = %s" $j [expr $j*12]]
    set j [expr $j + 1]
}

Jak vidíte, struktura příkazu while je docela podobná. Pouze místo WEND v jazyce BASIC se zde používají složené závorky. Ale co to je za zmatek v těle cyklu? Pamatujete si ještě na použití formátovacího řetězce v jazyce Python? Zápis format je jeho jeho ekvivalentem v jazyce Tcl. Zápis $j vyjadřuje hodnotu proměnné j (takto se odliší od zápisu písmene 'j') a expr prostě říká vyhodnoť následující zápis jako výraz. Hranaté závorky Tcl říkají, které části se mají zpracovávat nejdřív. Tcl je poněkud neobvyklý jazyk v tom, že provádí interpretaci svého kódu v jednom průchodu. Takže bez závorek by se pokoušel vytisknout slovo 'expr', potom by zpozoroval další hodnoty a po výpisu chybového hlášení by to vzdal. Potřebujeme mu říct, že má nejdříve provést součet, potom naformátovat řetězec a vytisknout výsledek. Jste zmateni? Nedělejte si s tím starosti. Jak už jsem řekl, Tcl je poněkud neobvyklý jazyk s několika výjimečně dobrými možnostmi a s velkým množsvím podivností.

A nyní se podívejme na Python:

>>> j = 1
>>> while j <= 12:
...     print "%d x 12 = %d" % (j, j*12)
...     j = j + 1

V tomto okamžiku už by se vám to mohlo zdát docela jasné. Chtěl bych vás jen upozornit na jednu věc. Vidíte tu dvojtečku na konci řádku s příkazem while a před tím na konci řádku s for? Právě ta překladači jazyka Python říká, že bude následovat úsek kódu (blok). Většina jazyků používá pro označení konce bloku nějakou značku (například BASIC používá WEND, Tcl složené závorky), ale Python pro vyjádření struktury používá odsazení. To znamená, že je důležité dodržet stejné odsazení všech řádků uvnitř cyklu. Ono to stejně patří k dobrým zásadám, které zvyšují čitelnost kódu.

Pružnější zápis cyklů

Vraťme se zpět k naší tabulce násobení číslem 12 ze začátku této kapitoly. Cyklus, který jsme vytvořili, se pro tisk takové tabulky velmi dobře hodí. Ale jak by to bylo s jinými hodnotami? Mohli bychom cyklus upravit tak, aby produkoval tabulku násobků třeba číslem 7? Mělo by to vypadat nějak takto:

>>> for j in range(1, 13):
...     print "%d x 7 = %d" % (j, j*7)

Při úpravě jsme museli hodnotu 12 změnit na hodnotu 7 a to na dvou místech. A pokud bychom chtěli použít jinou hodnotu, museli bychom ji, opět na dvou místech, změnit znovu. Nebylo by lepší, kdybychom mohli nějakým obecnějším způsobem zadat požadovaného násobitele?

Můžeme toho dosáhnout tím, že místo konkrétní hodnoty použijeme další proměnnou. Hodnotu této proměnné nastavíme před zahájením cyklu:

>>> nasobitel = 12
>>> for j in range(1,13):
...     print "%d x %d = %d" % (j, nasobitel, j*nasobitel)

Takto získáme naši starou známou tabulku násobení číslem 12. Ale pokud nyní budeme chtít násobit sedmi, stačí, když změníme pouze hodnotu proměnné nasobitel.

Povšimněte si, že zde kombinujeme zápis posloupnosti příkazů a příkaz cyklu. Nejdříve jsme použili jednoduchý příkaz nasobitel = 12, za kterým následuje v pořadí další příkaz cyklu for.

Vnořené cykly

Použijme nyní předchozí příklad k dalšímu kroku. Dejme tomu, že chceme vytisknout všechny tabulky násobků čísel od 2 do 12 (násobení číslem 1 je příliš jednoduché než abychom se jím zabývali). Jediné, co musíme učinit, je použít proměnnou nasobitel jako součást dalšího cyklu:

>>> for nasobitel in range(2, 13):
...     for j in range(1, 13):
...         print "%d x %d = %d" % (j, nasobitel, j*nasobitel)

Všimněte si, že odsazená část uvnitř prvního cyklu for je zápisem přesně téhož cyklu, s kterým jsme začínali. Funguje to následovně: Nastavíme nasobitel na první hodnotu (2) a provedeme druhý cyklus. Poté hodnotu proměnné nasobitel změníme na následující hodnotu (3) a znovu provedeme vnitřní cyklus, a tak dále. Tato technika je známa jako vnořování cyklů.

Drobnou nepříjemností je to, že se nám všechny tabulky spojí dohromady. Můžeme ji odstranit tím, že na konci prvního cyklu vytiskneme oddělovací čáru:

>>> for nasobitel in range(2, 13):
...     for j in range(1, 13):
...         print "%d x %d = %d" % (j, nasobitel, j*nasobitel)
...     print "-------------------"

Všimněte si, že druhý příkaz print je odsazený o stejnou hodnotu, jako řádek s druhým cyklem for — jde tedy o druhý příkaz v posloupnosti příkazů cyklu. (Prvním příkazem je zde vnořený cyklus.) Zapamatujte si, že úroveň odsazení je v jazyce Python velmi důležitá.

Pokuste se vytvořit oddělovač tabulek, který by říkal, jaká tabulka mu předchází — popis pod tabulkou. Nápověda: Pravděpodobně budete muset použít proměnnou nasobitel a formátovací řetězec.

Ostatní typy cyklů

Některé jazyky umožňují více typů konstrukcí cyklu, ale obvykle podporují něco jako for a while. (Modula 2 a Oberon poskytují pouze cykly typu while, protože cykly for jimi můžeme nasimulovat — jak jsme viděli výše.) Jiné typy cyklů, se kterými se můžete setkat jsou:

do-while
Tento typ cyklu je stejný jako while, ale test se provádí až na konci, za tělem cyklu. To znamená, že se cyklus provede vždy alespoň jednou.
repeat-until
Podobá se předchozímu typu s tím, že logika testu je opačná.
GOTO, JUMP, LOOP, atd.
Lze se s nimi setkat hlavně ve starších jazycích. V kódu se obvykle definuje značka a skáče se přímo na takto označené místo.

Zapamatujte si

Pokud vás napadne, co by se dalo na překladu této kapitoly vylepšit, zašlete e-mail odklepnutím tohoto odkazu. Tím bude do subjektu dopisu automaticky vložena informace o jméně a verzi tohoto HTML dokumentu.

$Id: cztutloops.html,v 1.2 2004/05/14 10:22:46 prikryl Exp $