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 $