Obsah
1. Konstrukce pro řízení běhu programu v jazyce Go
2. Volání funkcí a příkaz return
3. Příkaz return umístěný před koncem těla funkce
4. Jednoduchá rozhodovací konstrukce typu if
5. Rozvětvení s využitím konstrukce if-else
6. Příkaz zapsaný za klíčovým slovem if
7. Rozvětvení běhu programu s využitím konstrukce switch
8. Porovnání výrazu s konstantami a vypočtenými hodnotami v konstrukci switch
9. Vyhodnocení a porovnání výsledků podmínek zapsaných ve větvích case
10. Větší množství větví case se společným tělem: klíčové slovo fallthrough
11. Další příklady použití rozvětvení typu switch
12. Programové smyčky v jazyku Go: příkaz for
13. Příkaz for s podmínkou na začátku
14. Varianta programové smyčky převzatá z jazyka C
15. Iterace nad datovými strukturami s využitím for a range
16. Ovlivnění programových smyček příkazy break a continue
17. Příkazy break a continue ve vnořených smyčkách
18. Návrat do minulosti: příkaz goto
19. Repositář s demonstračními příklady
1. Konstrukce pro řízení běhu programu v jazyce Go
Většina informací, které jsme si prozatím o Go řekli, se týkala především typového systému tohoto programovacího jazyka. Typový systém totiž hraje v této skupině jazyků (silné typování hlídané již v době překladu a doplněné o dynamickou „implementaci“ rozhraní kontrolovanou v runtime) velmi důležitou roli. Dokonce by se mohlo říci, že u rozsáhlejších aplikací jsou právě vlastnosti typového systému významnější, než například syntaxe jazyka, která poněkud ustupuje do pozadí (ještě do větší míry je to patrné u programovacího jazyku Rust, v němž se od typového systému odvíjí například i správa paměti).
Nicméně pro tvorbu reálných aplikací samozřejmě nevystačíme pouze se znalostí typového systému jazyka. Musíme mj. i vědět, jak je v něm řešeno větvení (rozhodovací konstrukce) a popř. i programové smyčky (pokud nejsou vyřešeny rekurzí a popř. i koncovou rekurzí TCO, což je ovšem většinou doména funkcionálních programovacích jazyků). Z tohoto důvodu se dnes seznámíme s těmi jazykovými konstrukcemi, které slouží pro takzvané řízení běhu programu.
V případě programovacího jazyka Go se konkrétně jedná o implementaci rozhodovacích konstrukcí a taktéž o implementaci různých typů programových smyček. To, že konstrukce sloužící pro řízení běhu programu neodmyslitelně ke Go patří, je dobře ilustrováno i na seznamu klíčových slov tohoto jazyka, které jsou vypsány v tabulce umístěné pod tímto odstavcem. Všechna klíčová slova, která se k tomuto tématu vztahují, jsou zvýrazněna odkazem (na příslušnou kapitolu):
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
2. Volání funkcí a příkaz return
Připomeňme si jen ve stručnosti, že základním stavebním prvkem, z něhož je možné v jazyce Go skládat složitější algoritmy, jsou funkce. Ty mají jeden vstupní bod (entry point) a pokud ve funkci explicitně nepoužijeme příkaz return (a funkce tudíž nevrací žádnou hodnotu), mají i jediný bod výstupní (exit point). Deklarace funkce začíná klíčovým slovem func, za kterým následuje nepovinné označení příjemce (receiver), povinné jméno funkce, parametry funkce (pokud je funkce bez parametrů, použijí se prázdné kulaté závorky), návratový typ a konečně tělo funkce umístěné do složených závorek:
package main func emptyFunction() { fmt.Println(message) } func printMessage(message string) { fmt.Println(message) } func swap(a int, b int) (int, int) { return b, a } func (line Line) length() float64 { return math.Hypot(line.x1-line.x2, line.y1-line.y2) } func (line Line) translate(dx, dy float64) { fmt.Printf("Translating line %v by %f %f\n", line, dx, dy) line.x1 += dx line.y1 += dy line.x2 += dx line.y2 += dy } func main() { println("Hello world!") emptyFunction() printMessage("pokus") x := 1 y := 2 var z int var w int z, w = swap(x, y) fmt.Println("z =", z) fmt.Println("w =", w) ... ... ... }
S prvním příkazem sloužícím pro řízení běhu programu jsme se již vlastně setkali v úvodním článku. Jedná se o příkaz return, který provádí dvě související operace:
- Ukončuje právě prováděnou funkci a vrací řízení zpět volajícímu bloku.
- Výrazy zapsané za příkaz return jsou vyhodnoceny a vypočtené hodnoty jsou vráceny volající funkci (již víme, že návratových hodnot může být více, což se ostatně v jazyku Go velmi často využívá).
V dnešním druhém demonstračním příkladu je ukázán základní způsob použití příkazu return ve chvíli, kdy funkce, ve které je return použit, nevrací žádné hodnoty:
package main func f1() { println("f1") return } func main() { println("Hello world!") f1() }
3. Příkaz return umístěný před koncem těla funkce
Zajímavé je, že překladači programovacího jazyka Go nebude vadit ani funkce, v níž se vyskytuje větší množství příkazů return ve stejném bloku, i když je zřejmé, že už první return běh funkce ukončí a další příkazy z tohoto důvodu nebudou vykonány. Opět si můžeme toto chování vyzkoušet na dalším demonstračním příkladu:
package main func f1() { println("f1() před příkazem return") return println("f1() po příkazu return") } func main() { println("Hello world!") f1() }
Překlad tohoto příkladu kupodivu proběhne bez toho, že by se vypsalo jakékoli varování. Chování programu v runtime je následující:
Hello world! f1() před příkazem return
Z tohoto výpisu je patrné, že se skutečně běh funkce ukončil na prvním příkazu return, takže se druhé volání funkce println nikdy neuskutečnilo.
Z předchozího popisu by se mohlo zdát, že překladač programovacího jazyka Go neprovádí při překladu funkcí s příkazem return žádnou kontrolu chyb. Ve skutečnosti to ovšem není pravda, protože ve chvíli, kdy budeme překládat funkci s návratovou hodnotou (nebo hodnotami), bude překladač zjišťovat, jestli funkce ve všech možných případech vrátí deklarovaný počet a typ hodnot. To znamená, že se testují příkazy return ve všech větvích, na nedosažitelných řádcích(!) atd.
Ukažme si nyní – prozatím na velmi jednoduchém příkladu – jak taková kontrola probíhá v praxi. V následujícím zdrojovém kódu sice funkce s návratovým typem int skutečně obsahuje příkaz return s celočíselným výrazem, ovšem za tímto příkazem se nachází další příkaz(y). Ty jsou sice (v daný okamžik) nedosažitelné, překladač ovšem i přesto bude vyžadovat, aby se tato část programového bloku ukončila pomocí return s celočíselným výrazem:
package main func f2() int { println("f2() před příkazem return") return 42 println("f2() po příkazu return") } func main() { println("Hello world!") println(f2()) }
Pokus o překlad tedy skončí s chybou:
./04_return_statement_int_value.go:14:1: missing return at end of function
Oprava této chyby (popř. její ukrytí před překladačem!) je samozřejmě velmi snadná:
package main func f2() int { println("f2() před příkazem return") return 42 println("f2() po příkazu return") return -1 } func main() { println("Hello world!") println(f2()) }
4. Jednoduchá rozhodovací konstrukce typu if
Základní konstrukcí programovacího jazyka Go určenou pro rozvětvení běhu programu na základě nějaké podmínky je – podobně jako v prakticky všech ostatních vysokoúrovňových jazycích – příkaz if. Chování tohoto příkazu i způsob jeho zápisu je ovšem v Go odlišný, než je tomu v C/C++/Javě. První změna je patrná na první pohled – okolo výrazu, jehož hodnota rozhodne o rozvětvení, se nemusí psát závorky.
Naopak je však nutné (a to na stejném řádku) zapsat levou složenou závorku za tímto výrazem, protože v případě, že je podmínka splněna, se vždy vykoná celý blok příkazů. Tento blok samozřejmě může obsahovat jen jediný příkaz, ovšem i v tomto případě je nutné blok použít. Toto je velmi dobrá vlastnost Go, protože umožnění zápisu pouze jediného příkazu za if bez použití bloku může vést k těžko odhalitelným chybám (pravděpodobně nejznámější chybou tohoto typu je Apple goto fail). A konečně: výraz zapsaný za if musí být typu boolean, což je striktně kontrolováno překladačem.
Samozřejmě si ukážeme příklady použití konstrukce if. Její nejjednodušší podoby vypadají následovně:
package main func main() { if true { println("true") } if false { println("false") } if !true { println("false") } if !false { println("true") } var b1 bool = true if b1 { println("true") } if !b1 { println("false") } b2 := true if b2 { println("true") } if !b2 { println("false") } }
Jak jsme si již řekli v předchozích odstavcích, musí být výraz zapsaný za if striktně typu boolean. Navíc typový systém programovacího jazyka Go neumožňuje automatické (implicitní) přetypování hodnot/výrazů jiného typu na boolean, takže příklad zobrazený níže se nepodaří přeložit (což je z pohledu rozsáhlejších aplikací jen dobře):
package main func main() { if 1 { println("true") } if 0 { println("false") } if !1 { println("false") } if !0 { println("true") } var b1 int = 1 if b1 { println("true") } if !b1 { println("false") } b2 := 1 if b2 { println("true") } if !b2 { println("false") } }
Překladač si v této chvíli správně postěžuje na to, že výraz za příkazem if není typu boolean:
./07_if_statement_bad_type.go:11:2: non-bool 1 (type int) used as if condition ./07_if_statement_bad_type.go:15:2: non-bool 0 (type int) used as if condition ./07_if_statement_bad_type.go:19:5: invalid operation: ! untyped number ./07_if_statement_bad_type.go:23:5: invalid operation: ! untyped number ./07_if_statement_bad_type.go:29:2: non-bool b1 (type int) used as if condition ./07_if_statement_bad_type.go:32:5: invalid operation: ! int ./07_if_statement_bad_type.go:38:2: non-bool b2 (type int) used as if condition ./07_if_statement_bad_type.go:41:5: invalid operation: ! int
Totéž ovšem platí i pro hodnotu nil, která se v Go nedá automaticky přetypovat na false (v mnoha dalších jazycích, včetně C, Luy, Pythonu, Lispu či Clojure, je chování odlišné):
package main func main() { if nil { println("true") } if !nil { println("false") } if "" { println("true") } if !"" { println("false") } var b1 bool = nil if b1 { println("true") } if !b1 { println("false") } }
Opět si ukažme výsledek pokusu o překlad tohoto příkladu:
./08_if_statement_nil.go:11:2: use of untyped nil ./08_if_statement_nil.go:15:5: invalid operation: ! nil ./08_if_statement_nil.go:19:2: non-bool "" (type string) used as if condition ./08_if_statement_nil.go:23:5: invalid operation: ! untyped string ./08_if_statement_nil.go:27:6: cannot use nil as type bool in assignment
5. Rozvětvení s využitím konstrukce if-else
Podobně jako v dalších programovacích jazycích je možné i v jazyce Go doplnit příkaz if o větev else, takže se namísto jednoduchého větvení může provést úplné rozvětvení. Pro větev else platí stejná pravidla, jako pro samotný if: vždy se používá blok příkazů uzavřený mezi složené závorky a navíc by se levá (otevírací) složená závorka měla zapsat na stejný řádek, jako samotné klíčové slovo else. Opět si vše ukážeme na jednoduchém příkladu. V první funkci je provedeno rozhodnutí na základě dvou podmínek a došlo zde tedy k zřetězení else s dalším if, ve funkci main se používá pouze úplné rozvětvení if-else:
package main func classify_char(c rune) string { if c >= 'a' && c <= 'z' { return "male pismeno" } else if c >= 'A' && c <= 'Z' { return "velke pismeno" } else { return "neco jineho" } } func main() { if true { println("true") } else { println("false") } if !true { println("false") } else { println("true") } println(classify_char('a')) println(classify_char('Z')) println(classify_char('?')) }
Zajímavé je, že v Go zdánlivě neexistuje konstrukce určená pro explicitní zřetězení několika podmínek, která je obvykle zapisovaná klíčovým slovem elif nebo elseif. Pokud budeme chtít provést rozvětvení na základě několika podmínek, máme k dispozici dvě základní možnosti:
- Použít již výše zmíněnou a ukázanou sekvenciif-else if-else if-else
- Nebo můžeme použít jednu z variant konstrukceswitch-case, s níž se seznámíme v dalších kapitolách. Možnosti této konstrukce jsou totiž v jazyce Go mnohem větší, než je tomu v již několikrát zmiňované trojici C/C++/Java.
Při zápisu zdrojového kódu musíme dodržovat pravidla kontrolovaná překladačem jazyka Go. Mezi jedno takové pravidlo patří i to, že klíčové slovo else musí být zapsáno za pravou uzavírací závorku větve if. To například znamená, že se následující kód – který je sémanticky naprosto totožný s předchozím příkladem –, ve skutečnosti nepodaří přeložit:
package main func classify_char(c rune) string { if c >= 'a' && c <= 'z' { return "male pismeno" } else if c >= 'A' && c <= 'Z' { return "velke pismeno" } else { return "neco jineho" } } func main() { println(classify_char('a')) println(classify_char('Z')) println(classify_char('?')) }
Překladač by měl vypsat následující chyby (nikoli pouhé varování):
./10_bad_syntax.go:11:25: syntax error: unexpected newline, expecting { after if clause ./10_bad_syntax.go:15:2: syntax error: unexpected else, expecting }
6. Příkaz zapsaný za klíčovým slovem if
V programovacím jazyku Go je možné ještě před vlastní podmínku vložit nějaký příkaz, který je od podmínky oddělen středníkem. V tomto příkazu se typicky deklaruje a inicializuje nějaká lokální proměnná. Celý zápis může vypadat následovně:
package main func funkce() int { return -1 } func x() string { if value := funkce(); value < 0 { return "záporná hodnota" } else if value > 0 { return "kladná hodnota" } else { return "nula" } } func main() { println(x()) }
package main func funkce() int { return -1 } func x() string { if value := funkce(); value < 0 { return "záporná hodnota" } else if value > 0 { return "kladná hodnota" } else { return "nula" } println(value) return "" } func main() { println(x()) }
Překladač nyní správně vypíše chybu:
prog.go:21:10: undefined: value ./10C_statement_in_if_not_visible.go:21:10: undefined: value
7. Rozvětvení běhu programu s využitím konstrukce switch
V programovacím jazyce Go nalezneme i konstrukci switch s větvemi, které začínají klíčovým slovem case. Tato konstrukce vývojářům nabízí mnohem větší možnosti využití, než je tomu v C/C++ či Javě, což ostatně bude patrné i při pohledu na demonstrační příklady. Prvním vylepšením je fakt, že se za klíčové slovo switch nemusí zapisovat žádný výraz. V takovém případě se předpokládá, že (neexistující) výraz je vyhodnocen na hodnotu true. Poněkud umělý příklad, který této vlastnosti využívá, může vypadat následovně:
package main func main() { switch { } switch { default: println("proč jsem vlastně použil switch?") } switch { case true: println("true") case false: println("false") } switch { case false: println("false") case true: println("true") default: println("default") } switch { case false: println("false") default: println("default") case true: println("true") } }
První switch je zcela prázdný, což je povoleno. Druhý switch obsahuje pouze větev uvozenou klíčovým slovem default, která je v tomto případě spuštěna. Zajímavý je poslední příklad, který ukazuje, že větve není (většinou) zapotřebí za sebe řadit takovým způsobem, aby default byla až na konci.
Výsledek, který získáme po spuštění tohoto příkladu:
proč jsem vlastně použil switch? true true true
8. Porovnání výrazu s konstantami a vypočtenými hodnotami v konstrukci switch
Příklad z předchozí kapitoly ve skutečnosti nebyl vůbec praktický, takže si ukažme některé skutečně užitečné vlastnosti konstrukce switch. V jazyce Go je možné u každé větve case specifikovat výčet hodnot, pro které se má daná větev provést. Můžeme tedy bez problémů určit nulovou hodnotu, sudá čísla a lichá čísla pro všechna přirozená čísla od 0 do 9 (což je opět spíše umělý příklad, který by se v praxi řešil jinak):
package main func classify(x int) string { switch x { case 0: return "nula" case 2, 4, 6, 8: return "sudé číslo" case 1, 3, 5, 7, 9: return "liché číslo" default: return "?" } } func main() { for x := 0; x <= 10; x++ { println(x, classify(x)) } }
Výsledky:
0 nula 1 liché číslo 2 sudé číslo 3 liché číslo 4 sudé číslo 5 liché číslo 6 sudé číslo 7 liché číslo 8 sudé číslo 9 liché číslo 10 ?
Dále je umožněno, aby se u větví case nepoužívaly pouze konstanty. V níže uvedeném zdrojovém kódu je namísto konstanty 0 použit parametr zero_value, což je zcela legální:
package main func classify(x int, zero_value int) string { switch x { case zero_value: return "nula" case 2, 4, 6, 8: return "sudé číslo" case 1, 3, 5, 7, 9: return "liché číslo" default: return "?" } } func main() { for x := 0; x <= 10; x++ { println(x, classify(x, 0)) } }
Výsledky:
0 nula 1 liché číslo 2 sudé číslo 3 liché číslo 4 sudé číslo 5 liché číslo 6 sudé číslo 7 liché číslo 8 sudé číslo 9 liché číslo 10 ?
9. Vyhodnocení a porovnání výsledků podmínek zapsaných ve větvích case
Možnosti konstrukce switch jdou ovšem ještě dále, a to z toho důvodu, že se switch v Go spíše podobá formě cond známé z lispovských jazyků. U jednotlivých větví case totiž můžeme zapsat výrazy s typem boolean. V takovém případě se ovšem žádný výraz naopak nezapisuje přímo za switch. Předchozí příklady můžeme přepsat tak, aby byly obecnější a neomezovaly se jen na malá přirozená čísla:
package main func classify(x int) string { switch { case x == 0: return "nula" case x%2 == 0: return "sudé číslo" case x%2 == 1: return "liché číslo" default: return "?" } } func main() { for x := 0; x <= 10; x++ { println(x, classify(x)) } }
Výsledky po spuštění:
0 nula 1 liché číslo 2 sudé číslo 3 liché číslo 4 sudé číslo 5 liché číslo 6 sudé číslo 7 liché číslo 8 sudé číslo 9 liché číslo 10 sudé číslo
10. Větší množství větví case se společným tělem: klíčové slovo fallthrough
V sedmé kapitole jsme se zmínili o tom, že se v konstrukci switch-case jednotlivé větve neukončují pomocí break. Je tomu tak z toho důvodu, že větve jsou ukončeny zcela automaticky, což je odlišné chování, než jaké známe z C/C++/Javy!
Výsledkem je, že následující program se nebude chovat přesně tak, jak by tomu bylo ve zmíněných jazycích:
package main func classify(x int) string { switch x { case 0: return "nula" case 2: case 4: case 6: case 8: return "sudé číslo" case 1: case 3: case 5: case 7: case 9: return "liché číslo" default: return "?" } return "X" } func main() { for x := 0; x <= 10; x++ { println(x, classify(x)) } }
Výsledek po spuštění:
0 nula 1 X 2 X 3 X 4 X 5 X 6 X 7 X 8 sudé číslo 9 liché číslo 10 ?
Pokud naopak budeme vyžadovat, aby některé větve měly společné tělo, musíme použít klíčové slovo fallthrough, které je přesným opakem break – zaručuje, že se bude pokračovat v dalších příkazech z navazující větve:
package main func classify(x int) string { switch x { case 0: return "nula" case 2: fallthrough case 4: fallthrough case 6: fallthrough case 8: return "sudé číslo" case 1: fallthrough case 3: fallthrough case 5: fallthrough case 7: fallthrough case 9: return "liché číslo" default: return "?" } } func main() { for x := 0; x <= 10; x++ { println(x, classify(x)) } }
Výsledek po spuštění tohoto příkladu je již přijatelnější:
0 nula 1 liché číslo 2 sudé číslo 3 liché číslo 4 sudé číslo 5 liché číslo 6 sudé číslo 7 liché číslo 8 sudé číslo 9 liché číslo 10 ?
11. Další příklady použití rozvětvení typu switch
Příkaz fallthrough je samozřejmě možné použít i ve chvíli, kdy se používají větve case s podmínkami:
package main func classify(x int) string { switch { case x == 0: return "nula" case x%2 == 1: return "liché číslo" case x%2 == 0: fallthrough default: return "sudé číslo" } } func main() { for x := 0; x <= 10; x++ { println(x, classify(x)) } }
Jen pro úplnost se zmiňme o tom, že pro řetězce je funkční operátor porovnání a tudíž můžeme provést rozeskok i na základě hodnoty řetězce (což například v C není takto přímo možné):
package main func command(x string) string { switch x { case "": return "missing command" case "help": fallthrough case "info": return "help" case "bye": fallthrough case "exit": fallthrough case "quit": return "quit" default: return "unknown command" } return "unknown command" } func main() { println(command("")) println(command("bzz bzz bzz")) println(command("bye")) println(command("quit")) println(command("exit")) }
12. Programové smyčky v jazyku Go: příkaz for
Po popisu jazykových konstrukcí určených pro větvení a rozvětvení běhu programu se (konečně) dostáváme k popisu programových smyček. V programovacím jazyku Go jsou sice všechny typy smyček realizovány jediným klíčovým slovem for, ovšem ve skutečnosti lze toto (doslova) klíčové slovo použít pro implementaci čtyř typů smyček:
- Nekonečná smyčka (zdánlivě neužitečná konstrukce se ovšem může použít společně s příkazy break nebo return).
- Smyčka s testem prováděným na začátku každé iterace (tedy typická smyčka typu while).
- Iterace nad zvoleným datovým typem (pole, řez, řetězec, mapa apod.) přičemž – což je velmi užitečné – je v každé iteraci dostupný i příslušný selektor (index u polí, klíč u map).
- A konečně je podporován typ zápisu smyčky, který je odvozen od klasického céčka – za klíčové slovo for se zapisuje inicializační příkaz, výraz vyhodnocovaný pro ukončení smyčky a iterační příkaz. Tímto způsobem se v jazyce Go realizují počítané smyčky (což sice může vypadat jako určitý návrat do historie, ovšem jde o univerzální popis smyčky s počitadlem).
Vyzkoušejme si nejprve nekonečnou programovou smyčku. Tato smyčka se v jazyce Go zapisuje následujícím způsobem:
package main func main() { for { println("Diamonds are forever") } }
Programová smyčka zapsaná tímto způsobem je skutečně nekonečná, tj. řízení programu v ní zůstane tak dlouho, dokud není program zastaven nějakým zásahem zvenku. V našem případě je nejjednodušší stisknout klávesovou zkratku Ctrl+C, popř. použít příkaz kill. K čemu je však tento typ smyčky vhodný? Použijeme ji ve chvíli, kdy je podmínka pro ukončení iterací zjišťována nikoli na samotném začátku smyčky (před vstupem do bloku s příkazy), ale buď na konci smyčky nebo někde uprostřed. V takovém případě se smyčka ukončí s využitím konstrukce:
for { ... ... ... if podmínka { break } ... ... ... }
for ; ; { }
13. Příkaz for s podmínkou na začátku
Druhá varianta programové smyčky for obsahuje výraz, který se vyhodnocuje vždy před začátkem každé iterace, tj. před každým vstupem do bloku představujícího tělo smyčky. Jedná se tedy o reimplementaci standardní řídicí konstrukce typu while. V programovacím jazyku Go se tento typ smyčky zapisuje následujícím způsobem:
for podmínka { ... ... ... }
Předchozí příklad tedy můžeme nepatrně přepsat do explicitního tvaru:
package main func main() { for true { println("Diamonds are forever") } }
Opět si ukažme, jak se tato smyčka použije v praxi při testování, zda se má provést další iterace či nikoli. V následujícím příkladu se odpočítává od desíti do jedné:
package main func main() { i := 10 for i != 0 { println(i) i-- } }
Výsledek po spuštění:
10 9 8 7 6 5 4 3 2 1
for ; podmínka ; { }
14. Varianta programové smyčky převzatá z jazyka C
Třetí varianta programové smyčky je odvozena od céčkové konstrukce se shodným názvem for. Tato konstrukce vyžaduje, aby za klíčovým slovem for byla zapsána následující trojice:
- Inicializační příkaz; typicky se jedná o inicializaci nějaké existující proměnné, nebo – a to častěji – deklaraci nové lokální proměnné spojené s její inicializací (to se provádí operátorem :=).
- Výraz, který je vyhodnocen před každou iterací. Na základě hodnoty tohoto výrazu se zjišťuje, zda se má skutečně provést další iterace či nikoli. Tento výraz musí být typu boolean.
- Příkaz provedený na konci každé iterace. Typicky se v něm snižuje či zvyšuje (obecně mění) hodnota lokální proměnné využité ve funkci počitadla.
Mezi tyto tři syntaktické prvky se vkládá středník. Navíc pokud není zapotřebí provést inicializaci proměnné před spuštěním smyčky, je možné první prvek zcela vynechat. Podobně lze vynechat prvek třetí v případě, že nepotřebujeme provádět nějaký příkaz na konci každé iterace. A konečně – vynechat je možné i samotnou podmínku – v tomto případě je použita výchozí podmínka, která je vždy splněna (jakoby se namísto výrazu doplnila hodnota true – v tomto případě je však vhodnější vynechat i samotné středníky a realizovat tak vlastně první popsaný typ programové smyčky, který je mnohem čitelnější).
Podívejme se nyní, jakým způsobem je možné v programovacím jazyce Go realizovat jednoduchou smyčku s počitadlem. První varianta příkladu používá lokální proměnnou deklarovanou před vlastní smyčkou a tudíž platnou i po doběhnutí smyčky:
package main func main() { var i int for i = 0; i < 10; i++ { println(i) } println() println(i) }
Druhá varianta příkladu, v níž je lokální proměnná (takzvaná řídicí proměnná smyčky) deklarována a inicializována přímo v rámci příkazu for, je v praxi používána mnohem častěji:
package main func main() { for i := 0; i < 10; i++ { println(i) } }
Důležité je, že v tomto případě se skutečně jedná o proměnnou viditelnou pouze v rámci programové smyčky. To znamená, že za vlastní smyčkou již tato proměnná nebude dostupná (a bude tedy například možné vytvořit druhou smyčku používající stejnou proměnnou). Přesvědčíme se o tom snadno:
package main func main() { for i := 0; i < 10; i++ { println(i) } println(i) }
Proměnná i skutečně přestane být po doběhnutí smyčky viditelná:
./24_better_for_local_variable.go:14:10: undefined: i
15. Iterace nad datovými strukturami s využitím for a range
Nyní si již konečně dostáváme ke čtvrtému a současně i poslednímu typu programové smyčky nabízené jazykem Go. Tato programová smyčka je zapisována s využitím dvojice klíčových slov for a range, což je možná trošku matoucí, protože čtvrtý typ smyčky se používá pro průchod všemi prvky nějaké datové struktury (pole, řezu, řetězce, mapy atd.).
Kromě zpracování nějakého sekvenčního datového typu se tento typ programové smyčky používá i ve chvíli, kdy potřebujeme opakovaně načítat data z nějakého kanálu.
Podívejme se nyní, jaké dvojice hodnot se vrací v každé iteraci smyčky for range v závislosti na objektu, přes který se iteruje:
Typ | První hodnota | Druhá hodnota |
---|---|---|
pole | index (int) | hodnota i-tého prvku pole |
řez | index (int) | hodnota i-tého prvku řezu |
řetězec | index (int) | i-tý znak (runa) |
mapa | klíč (podle typu mapy) | hodnota přiřazená ke klíči |
kanál | položka umístěná do kanálu | × |
Nejprve si ukažme, jakým způsobem je možné procházet polem, řezem (slice) a taktéž řetězcem. V tomto případě se mezi klíčová slova for a range zapisuje dvojice identifikátorů, které reprezentují lokální proměnné platné pouze uvnitř těla smyčky. První z proměnných bude obsahovat selektor, v tomto konkrétním případě index prvku pole/řezu, druhá proměnná pak bude obsahovat přímo hodnotu prvku z datové struktury, přes kterou se iteruje. Příklad vypadá následovně:
package main func main() { a := [...]int{1, 2, 10, -1, 42} for index, item := range a { println(index, item) } println() s := "Hello world ěščř Σ" for index, character := range s { println(index, character) } }
Povšimněte si, že existence selektoru může být v mnoha případech velmi užitečná (číslování řádků v generované tabulce atd.), ovšem selektor taktéž můžeme ignorovat, pokud ho nutně nebudeme v programu potřebovat. Musíme ovšem myslet na to, že nám Go nepovolí deklarovat proměnnou, která se nikde nepožije:
package main func main() { a := [...]int{1, 2, 10, -1, 42} for i, item := range a { println(item) } println() s := "Hello world ěščř Σ" for i, character := range s { println(character) } }
Pokus o překlad tohoto příkladu skončí s chybami:
./25B_for_range_without_index.go:13:6: i declared and not used ./25B_for_range_without_index.go:21:6: i declared and not used
Korektní je použití identifikátoru _ namísto skutečného jména proměnné, takže další příklad již půjde přeložit bez chyby:
package main func main() { a := [...]int{1, 2, 10, -1, 42} for _, item := range a { println(item) } println() s := "Hello world ěščř Σ" for _, character := range s { println(character) } }
Průchod mapou s využitím programové smyčky for si můžeme otestovat na dalším demonstračním příkladu. Po spuštění příkladu si povšimněte, že u map není obecně zachováno pořadí vkládání prvků, takže dvojice klíč-hodnota jsou procházeny v jiném pořadí, než v jakém byly do mapy vloženy:
package main func main() { var m1 map[int]string = make(map[int]string) m1[0] = "nula" m1[1] = "jedna" m1[2] = "dva" m1[3] = "tri" m1[4] = "ctyri" m1[5] = "pet" m1[6] = "sest" for key, val := range m1 { println(key, val) } }
Výsledek může vypadat následovně:
5 pet 6 sest 0 nula 1 jedna 2 dva 3 tri 4 ctyri
16. Ovlivnění programových smyček příkazy break a continue
Libovolnou variantu programové smyčky for je možné kdykoli ukončit příkazem break. Ten se typicky používá v bloku vykonaném při splnění nějaké podmínky, takže je například možné realizovat smyčku s druhým (či dalším) testem na konci každé iterace či dokonce uprostřed provádění iterace:
package main func main() { for i := 0; i < 10; i++ { println(i) if i == 5 { break } } }
Tento příklad má sice smyčku vytvořenou takovým způsobem, aby počitadlo dosáhlo hodnot v rozsahu 0 až 9, ve skutečnosti ovšem běh smyčky skončí dříve:
0 1 2 3 4 5
Druhý příkaz ovlivňující provádění programových smyček se jmenuje continue. V případě, že se tento příkaz vykoná, dojde k okamžitému přeskoku na další iteraci, ovšem u počítané smyčky se navíc ještě modifikuje obsah počitadla:
package main func main() { for i := 0; i < 10; i++ { if i%2 == 0 { continue } println(i) } }
Tento příklad vypíše pouze liché hodnoty, i když se ve smyčce počitadlo naplňuje všemi přirozenými čísly od 0 do 9:
1 3 5 7 9
17. Příkazy break a continue ve vnořených smyčkách
V jazyce Go samozřejmě můžeme smyčky vkládat do sebe. Příkladem může být výpočet tabulky s malou násobilkou:
package main import "fmt" func main() { for i := 1; i <= 10; i++ { for j := 1; j <= 10; j++ { fmt.Printf("%3d ", i*j) } fmt.Println() } }
Ten by měl vypsat:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
Pokud budeme například chtít najít, která dvojice čísel dá po svém součinu hodnotu 42, můžeme použít stejnou dvojici vnořených smyček, ovšem ve vnitřní smyčce je nutné zapsat podmínku, po jejímž splnění se ukončí jak vnitřní, tak i vnější smyčka. V tomto případě musíme použít příkaz break s takzvaným návěštím. To se zapisuje před tu smyčku, která se má ukončit (můžeme mít prakticky libovolné množství vnořených smyček). Za návěštím se vždy zapisuje dvojtečka:
package main import "fmt" func main() { Exit: for i := 1; i <= 10; i++ { for j := 1; j <= 10; j++ { fmt.Printf("%3d ", i*j) if i*j == 42 { fmt.Println("\nodpověď nalezena!\n") break Exit } } fmt.Println() } }
Výsledek bude vypadat takto:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 odpověď nalezena!
Alternativně je samozřejmě možné použít i příkaz continue s návěštím:
package main import "fmt" func main() { Exit: for i := 1; i <= 10; i++ { for j := 1; j <= 10; j++ { fmt.Printf("%3d ", i*j) if i*j == 42 { fmt.Println("\nodpověď nalezena!\nzkusím další řadu") continue Exit } } fmt.Println() } }
Nyní bude výstup programu samozřejmě zcela odlišný:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 odpověď nalezena! zkusím další řadu 7 14 21 28 35 42 odpověď nalezena! zkusím další řadu 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
18. Návrat do minulosti: příkaz goto
Zbývá nám popsat si ještě dvě řídicí konstrukce. První z nich se – což je pro moderní programovací jazyk alespoň na první pohled překvapivé – vytváří pomocí klíčového slova goto. Použitím tohoto klíčového slova je možné realizovat nepodmíněný skok v rámci jedné funkce nebo jednoho vybraného bloku. Příkaz goto je sice obecně považován za škodlivý (ostatně ho v novějších jazycích vůbec nenalezneme), ale existuje několik situací, kdy může jeho použití algoritmus zpřehlednit [1]. Jedná se například o implementaci konečného automatu, reakce na některé stavy programu apod. S goto se v jazyce Go setkáme jen málokdy, a to mj. i z toho důvodu, že je možné použít výskok z vnitřní smyčky (viz předchozí kapitolu) a pro zpracování chyb se používají odlišné konstrukce. Prozatím si tedy ani neukážeme žádný příklad, protože by byl umělý a spíše by ukazoval, jak se goto používat nemá :-)
Poslední jazyková konstrukce, která umožňuje (i když nepřímo) měnit běh programu, je realizována příkazem defer. Se způsobem použití tohoto příkazu se podrobněji seznámíme v navazující části seriálu.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/