O čem si budeme povídat?
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í.
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
. Mohou následovat i další
odsazené řádky. Python bude pro každý prvek z kolekce opakovat všechny
odsazené řádky. 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 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é 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 seznamu, 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.
<script type="text/vbscript"> For I = 1 To 12 MsgBox I & " x 12 = " & I*12 Next </script>
V tomto zápisu je mnohem lépe na první pohled vidět, co se děje. Hodnota
I
se mění od 1 až do 12 a provádí se kód uvedený před klíčovým
slovem Next
. V našem případě se výsledky jednoduše zobrazují v
dialogovém okně. Odsazení příkazu MsgBox
je
nepovinné, ale díky němu se kód čte snadněji.
Poznamenejme, že ačkoliv zápis v jazyce VBScript vypadá jasněji, pythonovská verze je mnohem pružnější. Proč, to uvidíme za chvíli.
Poznámka překladatele: Zápis příkladu v jazyce Python můžeme přiblížit podobě příkladu v jazyce VBScript tím, že nevyužijeme výhod použití 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) ...
Konstrukci for
, která je běžná ve více programovacích
jazycích, přebírá JavaScript z jazyka C. Vypadá takto:
<script type="text/javascript"> for (i = 1; i <= 12; i++) { document.write(i + " x 12 = " + i*12 + "<br>"); } </script>
Poznámka: Tato konstrukce má v kulatých závorkách uvedeny tři části:
i = 1
se provede
pouze jednou, před vším ostatním.i <= 12
se vykonává před každou iterací, tj. před každou obrátkou
cyklu.i++
, což je zkrácený zápis
příkazu pro "zvýšení proměnné i
o jedničku", se vykonává po
každé iteraci.Povšimněte si, že v JavaScript uzavíráme příkazy, které se mají opakovat
(tělo cyklu) do složených závorek {}
. Ačkoliv z
technického hlediska je to vše, co musíme s tělem cyklu udělat, z
praktického hlediska se považuje za vhodné, aby byl kód v závorkách odsazen.
Zvyšuje se tím čitelnost zdrojového textu.
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.
Poznámka překladatele: Povšimněte si použití operátoru pro zvýšení
proměnné cyklu o jedničku i++
. V tomto případě se jedná o
takzvaný postfixový operátor ++
. To znamená, že je
zapsán až za identifikátor proměnné. Kromě toho existuje
prefixová varianta téhož operátoru ++i
—
operátor je uveden před identifikátorem proměnné. Oba zvýší obsah
proměnné o jedničku. V čem se tedy jejich použití liší? V tomto případě v
ničem, ale...
Operátory ++
a --
pocházejí z jazyka C. V něm
celá řada příkazů může vystupovat jako výraz. To znamená, že vracejí nějakou
hodnotu a mohou tedy být použity například na pravé straně přiřazovacího
příkazu. Mimo jiné se to týká i samotného příkazu přiřazení a zmíněných
operátorů pro zvýšení a snížení o jedničku. Místo jinak běžného
zápisu...
a = 0; b = 0; c = 0; d = 0;
...tedy můžeme psát:
d = c = b = a = 0;
V tomto okamžiku nabývá prefixová a postfixová varianta zmíněných
operátorů svůj význam. Výsledkem postfixové varianty i++
je
totiž původní hodnota proměnné i
(nejdříve se získá hodnota a
potom se provádí zvýšení o jedničku), zatímco výsledkem prefixové
varianty ++i
je nová hodnota proměnné i
(nejdříve se provádí zvýšení o jedničku a pak se vrací hodnota
proměnné i
). Pokud v předchozím případě místo nuly použijeme
další proměnnou, jejíž obsah upravujeme variantami operátoru
++
, dostaneme po provedení příkazů na řádku v proměnných
hodnoty, které jsou uvedeny v komentářích:
i = 1; a = i++; /* a: 1, i: 2 */ b = ++i; /* b: 3, i: 3 */ c = ++i; /* c: 4, i: 4 */ d = i++; /* d: 4, i: 5 */
Při použití postfixové verze operátoru ++
musí překladač
jazyka udělat jeden krok navíc — původní hodnotu proměnné si musí
někam uložit (do registru nebo do jiné, skryté, pomocné proměnné) a teprve
potom může provést zvýšení původní proměnné o jedničku. Pokud překladač
neprování optimalizaci, pak je v případě, kdy se můžeme rozhodnout pro
prefixovou nebo postfixovou variantu příkazu ++
, vždy
efektivnější použít prefixovou variantu, tedy ++i
.
Výše uvedený příklad cyklu v JavaScript bychom tedy měli psát spíše
takto:
<script type="text/javascript"> for (i = 1; i <= 12; ++i) { document.write(i + " x 12 = " + i*12 + "<br>"); } </script>
Ale v jazyce Python musíme dát pozor! Ten totiž operátory
++
a --
vůbec nezná. A zatímco pro výrazy
i++
nebo i--
zahlásí chybu, pro výrazy
++i
nebo --i
nezahlácí vůbec nic. Pokud jsme z
jazyků rodiny C zvyklí používat ++i
, budeme se divit, proč to
nefunguje. Python jednoduše rozdělí jeden operátor na dvojici znamének. Na
zápis se tedy dívá jako na výraz (+(+i))
nebo
(-(-i))
, což z matematického hlediska znamená prázdnou
operaci.
Pythonovský cyklus for
prochází (říkáme také, že iteruje)
přes všechny prvky posloupnosti. Posloupností v jazyce Python — pro
případ, že byste zapoměli — je řetězec, seznam nebo n-tice. To
tedy znamená, že můžeme psát cykly for
, které zpracovávají
libovolný ze zmíněných typů. Vytiskněme si na zkoušku jednotlivá písmena
slova s využitím cyklu for
aplikovaného na řetězec:
>>> for znak in 'slovo': print znak ...
Povšimněte si, že se každé písmeno vytiskne na jeden řádek. Povšimněte si také, že pokud se tělo cyklu skládá z jediného řádku, můžeme je napsat na ten samý řádek, za dvojtečku. Právě dvojtečka říká překladači jazyka Python, že bude následovat blok kódu.
Iterovat můžeme i přes n-tici:
>>> for slovo in ('jedno', 'slovo', 'a', 'zas', 'jine'): print slovo ...
Tentokrát se nám na řádcích objeví jednotlivá slova. Mohli bychom je
samozřejmě při
tisku spojit na jeden řádek. Využijeme triku s uvedením čárky na
konci příkazu print
. Pokud zde uvedeme čárku, nebude Python
přecházet na další řádek a další tisk bude pokračovat tam, kde předchozí
skončil. Poznámka překladatele: Každá čárka v příkazu
print
vygeneruje oddělovací mezeru. Platí to i pro čárku na
konci příkazu print
. Vypsaná slova tedy budou uvedena na jednom
řádku a budou oddělena mezerou.
>>> for slovo in ('jedno', 'slovo', 'a', 'zas', 'jine'): print slovo, ...
Vidíte, jak se slova poskládala na jeden řádek?
Použití příkazu for
nad seznamem jsme již viděli, protože
dříve použitá
funkce range()
generuje právě seznam. Ale pro úplnost si
uveďme příklad s přímo zapsaným seznamem:
>>> for prvek in ['jedna', 2, 'tri']: print prvek ...
S uvedeným typem cyklu, který slouží k průchodu všemi prvky, je spojen jeden zádrhel. V průběhu dostáváte kopii toho, co se v procházené kolekci nachází. Obsah kolekce nemůžete měnit přímo. Pokud kolekci modifikovat potřebujeme, musíme použít nevzhledný obrat, který do hry zatahuje indexy prvků v kolekci:
mujSeznam = [1, 2, 3, 4] for index in range(len(mujSeznam)): mujSeznam[index] += 1 print mujSeznam
Uvedený program zvětšuje každou položku uvnitř mujSeznam
o
jedničku. Pokud bychom nepoužili trik s indexem, pak bychom pouze zvyšovali
hodnoty okopírovaných prvků, ale neměnili bychom prvky originálního
seznamu.
Poznámka překladatele 1: Ono to ve skutečnosti není tak přímočaré. Problematika modifikace seznamu souvisí s tím, že u některých objektů můžeme měnit hodnotu a u některých ne. Do seznamu se vždy vkládají odkazy na objekty. V uvedeném příkladu jsou těmito objekty celočíselné hodnoty, které nikdy nemůžeme měnit. Můžeme se na ně dívat jako na konstanty. Přičtením jedničky k číselné konstantě dostaneme jiné číslo — jinou konstantu, odkaz na zcela jiný objekt. Tento nový odkaz však s původním odkazem v seznamu nemá nic společného.
Pokud chceme dosáhnout toho, že se v seznamu objeví jiná čísla, musíme na příslušné pozice v seznamu uložit odkazy na jiné konstantní objekty s číslem. Situace by byla jiná, pokud bychom do seznamu zařadili objekty, které mohou během své existence měnit svůj stav. O tom ale až později.
Pokud této poznámce nerozumíte nebo vás děsí, nepropadejte panice. Je to úplně normální. Časem vám to bude jasné. Chtěl jsem jen, aby nad tím nesouhlasně nekroutili hlavou ti, kteří už tomu trochu víc rozumí.
Poznámka překladatele 2: V Pythonu verze 2.3 se objevil nový rys,
kterému bychom měli při řešení podobného problému dávat přednost. Místo
nepěkného obratu pro získání seznamu indexů bychom měli vždy použít mnohem
elegantnější a také výkonnější verzi, využívající zabudované funkce
enumerate()
:
mujSeznam = [1, 2, 3, 4] for index, hodnota in enumerate(mujSeznam): mujSeznam[index] = hodnota + 1 print mujSeznam
Použitím funkce enumerate()
se zajistí, že v každé obrátce
cyklu získáme sobě odpovídající dvojici (index, hodnota)
. V
uvedeném příkladu její složky přiřazujeme do stejnojmenných proměnných a
následně používáme. (... Nepropadejte panice!)
Další problém s cykly typu for
spočívá v
tom, že nemůžeme rušit prvky kolekce, přes kterou procházíme. Došlo by ke
zmatku. Podobá se to trochu situaci postavy ze starých grotesek, která
odřezává větev, na níž sedí. K řešení podobných situací se lépe hodí jiný typ
cyklu, o kterém si něco řekneme za chvíli. K porozumění problému bezpečného
odstraňování prvků kolekce však budeme potřebovat znalosti z další tématické
kapitoly, která je věnovaná větvení. Vysvětlení
naznačeného problému tedy uvedeme později.
Od verze Python 2.2 byly do jazyka přidány další triky, které činí cyklus
for
ještě mocnějším. Budeme se jimi zabývat později. Prozatím
stojí za to poznamenat, že i v jazycích VBScript a JavaScript existují
konstrukce cyklu pro průchod všemi prvky kolekce. Detaily se zde zabývat
nebudeme. Zápis konstrukce ve VBScript vypadá symbolicky takto:
for each ... in ...
. Zápis v jazyce
JavaScript vypadá takto: for ... in ...
. Pokud
máte zájem, můžete detailní popis nalézt na odpovídajících stránkách s
nápovědou.
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ější.
K řešení tohoto problému se hodí jiný typ cyklu — cyklus typu WHILE.
Zápis v jazyce Python vypadá takto:
>>> j = 1 >>> while j <= 12: ... print "%d x 12 = %d" % (j, j*12) ... j = j + 1
Projděme si, co jednotlivé příkazy dělají.
j
na 1
.
Nastavení počáteční hodnoty řídicí proměnné cyklu
while
představuje velmi důležitý první krok. Jeho opomenutí
bývá častou příčinou chyb.while
. V něm se
vyhodnocuje boolovský výraz.True
, dochází k
provedení následujícího odsazeného bloku. V našem případě nabývá
proměnná j
hodnoty menší než 12, takže zahájíme provádění
bloku kódu.print
, který vytiskne první řádek naší
tabulky.j
. V našem případě je to poslední stejně odsazený řádek, což
znamená, že blok cyklu while
končí.while
a provádíme kroky
4 až 6, vždy s novými hodnotami proměnné j
.j
dosáhne hodnoty 13.while
hodnotu
False
a provádění odsazeného bloku se přeskočí. Pokračovat
se bude řádkem, který má stejné odsazení, jako řádek s příkazem
while
.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).
Jiné jazyky, jak uvidíme za chvíli, definují své vlastní způsoby, jak
naznačit překladači skutečnost, že skupina řádků patří k sobě. Python
používá kombinaci dvojtečky a odsazení.
Podívejme se, jak vypadá zápis cyklu while
v jazyce
VBScript:
<script type="text/vbscript"> DIM J J = 1 While J <= 12 MsgBox J & " x 12 = " & J*12 J = J + 1 Wend </script>
Uvedený příklad produkuje stejné výsledky. Povšimněte si, že blok příkazů
cyklu je tentokrát uzavřen klíčovým slovem Wend
(což je
samozřejmě zkratka pro While End
). Až na tento rozdíl
příklad funguje naprosto stejně, jako jeho pythonovská verze.
<script type="text/javascript"> j = 1; while (j <= 12) { document.write(j, " x 12 = ", j*12, "<br>"); j = j + 1; } </script>
Jak vidíte, struktura programu je velmi podobná. Jen místo
Wend
(VBScript) se objevily složené závorky. Ani VBScript, ani
JavaScript (na rozdíl od Pythonu) nevyžadují jakékoliv odsazování. Kód se
odsazuje jen proto, aby byl čitelnější.
V JavaScript ještě stojí za to, abychom porovnali cykly for
a while
. Připomeňme, že cyklus for
vypadal nějak
takto:
for (j = 1; j <= 12; j++) {....}
Má tedy naprosto stejnou strukturu, jako cyklus while
, jen s
tím rozdílem, že je vše stlačeno do jednoho řádku. Jasně zde vidíme
inicializační část, testovanou podmínku a část úprav pro další obrátku
cyklu. Takže v jazyce JavaScript představuje cyklus for
pouze
kompaktnější formu cyklu while
. Bez cyklu for
bychom se mohli zcela obejít. Stačí nám pouze cyklus while
.
Některé jazyky volí právě takový přístup.
Poznámka překladatele: Podoba cyklu
for
je do JavaScript převzata z jazyka C. To, že kopíruje
činnost cyklu while
je známkou jeho nižší úrovně abstrakce.
(Jazyk C je někdy nazýván vysokoúrovňovým assemblerem.) Porovnejte
si stejný příklad opět s jazykem Python, kdy naopak musíme převést abstrakci
jednoduchého čísla na posloupnost hodnot, abychom vůbec mohli
konstrukci for
použít. Pythonovský cyklus for
je z
jazykového hlediska modernější. Až poznáte všechny jeho možnosti, určitě
nebudete ve většině případů dávat přednost cyklu while
.
Užitečnost cyklu pracujícího s vyššími abstrakcemi je také důvodem, proč
VBScript a JavaScript definují i dříve zmíněné formy cyklu pro iteraci přes
všechny prvky kolekce.
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
.
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ě:
nasobitel
na první hodnotu (2) a provedeme druhý
cyklus.nasobitel
změníme na následující
hodnotu (3) a znovu provedeme vnitřní cyklus,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á.
Jen pro porovnání uveďme, jak by to vypadalo v jazyce JavaScript:
<script type="text/javascript"> for (nasobitel = 2; nasobitel < 13; nasobitel++) { for (j = 1; j <= 12 ; j++) { document.write(j, " x ", nasobitel, " = ", j*nasobitel, "<br>"); } document.write("---------------<br>"); } </script>
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 pythonovský formátovací
řetězec.
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:
while
, ale test se provádí
až na konci, za tělem
cyklu. To znamená, že se cyklus provede vždy alespoň jednou.Zapamatujte si
FOR
opakuje sadu příkazů při pevně daném počtu
iterací (obrátek cyklu).WHILE
opakuje sadu příkazů dokud je splněna
podmínka. Může se stát, že se tělo cyklu neprovede nikdy. Stane se
tak v případě, kdy se pokračovací podmínka hned na začátku vyhodnotí jako
false (nepravda).FOR
a WHILE
jsou k dispozici téměř ve všech jazycích.for
v jazyce Python je skutečnosti cyklem typu
foreach
— pracuje nad položkami seznamu.Pokud vás napadne, co by se dalo na překladu této kapitoly vylepšit, zašlete e-mail odklepnutím Tím budou do dopisu automaticky vloženy informace o tomto HTML dokumentu.
$Id: cztutloops.html,v 1.11 2005/10/20 20:52:28 petr Exp $