Hlavní navigace

Konstrukce pro řízení běhu programu v jazyce Go

Pavel Tišnovský

Dnes si popíšeme všechny základní syntaktické konstrukce určené pro řízení běhu programu. Jedná se o různé formy větvení a samozřejmě taktéž o smyčky. Některé z dále popisovaných konstrukcí jsou až překvapivě flexibilní.

Doba čtení: 36 minut

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 forrange

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

20. Odkazy na Internetu

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
Poznámka: na tomto místě je vhodné již dopředu upozornit na fakt, že i když se v jazyce Go používají konstrukce, které mohou připomínat příkazy v programovacích jazycích C, C++, Java atd., je sémantika některých konstrukcí od céčkovských jazyků odlišná! Týká se to především konstrukce switch, v níž je zapotřebí explicitně určit ty větve case, které mají společné tělo. V C/C++/Javě a dalších jazycích je to přesně naopak – tam se naopak musí jednotlivé větve, které nemají mít společné příkazy, explicitně ukončovat příkazem break! Podrobnosti si samozřejmě ukážeme i na demonstračních příkladech. Taktéž je již dopředu dobré upozornit na to, že některé řídicí konstrukce v Go zdánlivě či zcela záměrně chybí. Jedná se konkrétně o programové smyčky while (je nahrazena univerzálnějším příkazem for) a taktéž o smyčku do-while.

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:

  1. Ukončuje právě prováděnou funkci a vrací řízení zpět volajícímu bloku.
  2. 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()
}
Poznámka: někteří vývojáři preferují použití takového stylu programování, v němž má každá funkce jeden vstupní a jediným výstupní bod, blíže viz například diskuse zde. V takovém případě se příkaz return použije vždy až na konci funkce, ovšem samotná logika může být složitější oproti situaci, kdy se return použije například uvnitř podmínky či v programové smyčce.

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())
}
Poznámka: otázkou samozřejmě zůstává, proč vlastně překladač umožňuje konstrukci return tímto způsobem používat. Jedním z důvodů je snadné „zakomentování“ části funkce při ladění programu (funkce se nahradí nějakým jednodušším ekvivalentem). Dále musí překladač počítat s konstrukcí goto (i ta v Go existuje). Teoreticky by ovšem nástroje pro statickou analýzu kódu mohly tyto minimálně podezřelé části detekovat.

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:

  1. Použít již výše zmíněnou a ukázanou sekvenciif-else if-else if-else
  2. 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.
Poznámka: překladač programovacího jazyka Go chápe celý řetězec if-else if-else atd. jako jediný složený příkaz, což má vliv především na oblast platnosti (viditelnosti) proměnné deklarované v rámci nepovinného příkazu uvedeného za prvním if (což je téma, kterému se budeme věnovat v navazující kapitole).

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())
}
Poznámka: povšimněte si, že hodnota value je dostupná i ve druhé větvi if, protože se jedná o jediný blok příkazů. Není ovšem viditelná za příkazem if:
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.

Poznámka: všimněte si chybějících příkazů break. Důvod, proč tyto příkazy ve větvích chybí, si popíšeme v navazujícím textu.

Výsledek, který získáme po spuštění tohoto příkladu:

proč jsem vlastně použil switch?
true
true
true
Poznámka: chybějící výraz za switch umožňuje zápis podmínek u jednotlivých větví case, což je velmi užitečné.

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
Poznámka: v tomto konkrétním případě se nikdy nevykoná větev default.

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 ?
Poznámka: nejsem si zcela jistý, ale mám pocit, že fallthrough je schválně zapisován takto dlouhým klíčovým slovem z toho důvodu, aby se příliš často nepoužíval.

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:

  1. 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).
  2. Smyčka s testem prováděným na začátku každé iterace (tedy typická smyčka typu while).
  3. 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).
  4. 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).
Poznámka: klíčové slovo for bylo s velkou pravděpodobností použito z toho důvodu, aby byl umožněn snadnější přechod vývojářů z mainstreamových jazyků na Go. Ovšem z dlouhodobějšího hlediska by možná bylo lepší a přesnější použít spíše klíčové slovo loop, zejména pro první dva typy programových smyček.

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
        }
        ...
        ...
        ...
}
Poznámka: ve skutečnosti je tento typ smyčky odvozen od dále popsaného „céčkovského“ typu smyčky, kde ovšem došlo k odstranění nepovinných výrazů. Z pohledu překladače se tedy jedná o ekvivalent tohoto zápisu (viz další text s podrobnějším popisem této smyčky):
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
Poznámka: striktně řečeno se opět jedná „pouze“ o zkrácený zápis tohoto typu smyčky:
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:

  1. 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 :=).
  2. 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.
  3. 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 forrange

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)
        }
}
Poznámka: s identifikátorem _, který označuje nevýznamnou hodnotu použitou pouze kvůli syntaktickým pravidlům, se můžeme setkat i v některých dalších programovacích jazycích. Jedná se například o jazyk Rust, s nímž jsme se na stránkách Rootu již setkali.

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:

# Demonstrační příklad Popis Cesta
1 01_main_function_only.go kostra programu s funkcí main https://github.com/tisnik/go-fedora/blob/master/article05/01_ma­in_function_only.go
2 02_return_statement.go příkaz return bez návratové hodnoty https://github.com/tisnik/go-fedora/blob/master/article05/02_re­turn_statement.go
3 03_return_statement_no_value.go více příkazů return https://github.com/tisnik/go-fedora/blob/master/article05/03_re­turn_statement_no_value.go
4 04_return_statement_int_value.go více příkazů return u funkce vracející int https://github.com/tisnik/go-fedora/blob/master/article05/04_re­turn_statement_int_value.go
5 05_return_statement_int_value.go více příkazů return u funkce vracející int https://github.com/tisnik/go-fedora/blob/master/article05/05_re­turn_statement_int_value.go
6 06_if_statement.go použití příkazu if https://github.com/tisnik/go-fedora/blob/master/article05/06_if_sta­tement.go
7 07_if_statement_bad_type.go příkaz if se špatným typem výrazu https://github.com/tisnik/go-fedora/blob/master/article05/07_if_sta­tement_bad_type.go
8 08_if_statement_nil.go příkaz if se špatným typem výrazu https://github.com/tisnik/go-fedora/blob/master/article05/08_if_sta­tement_nil.go
9 09_if_else_construct.go úplná konstrukce if-else https://github.com/tisnik/go-fedora/blob/master/article05/09_if_el­se_construct.go
10 10_bad_syntax.go špatné umístění závorky u else https://github.com/tisnik/go-fedora/blob/master/article05/10_bad_syn­tax.go
11 11_switch_statement.go použití příkazu switch https://github.com/tisnik/go-fedora/blob/master/article05/11_swit­ch_statement.go
12 12_constants_in_switch.go konstanty v konstrukci switch https://github.com/tisnik/go-fedora/blob/master/article05/12_con­stants_in_switch.go
13 13_variables_in_switch.go proměnné v konstrukci switch https://github.com/tisnik/go-fedora/blob/master/article05/13_va­riables_in_switch.go
14 14_conditions_in_switch.go konstrukce switch a podmínky u case https://github.com/tisnik/go-fedora/blob/master/article05/14_con­ditions_in_switch.go
15 15_no_fallthrough_in_switch.go chybějící klíčové slovo fallthrough https://github.com/tisnik/go-fedora/blob/master/article05/15_no_fa­llthrough_in_switch.go
16 16_fallthrough_in_switch.go použití klíčového slova fallthrough https://github.com/tisnik/go-fedora/blob/master/article05/16_fa­llthrough_in_switch.go
17 17_switch_combinations.go kombinace různých možností switch https://github.com/tisnik/go-fedora/blob/master/article05/17_swit­ch_combinations.go
18 18_switch_on_string.go switch a řetězce https://github.com/tisnik/go-fedora/blob/master/article05/18_swit­ch_on_string.go
19 19_simplest_for_loop.go nekonečná smyčka https://github.com/tisnik/go-fedora/blob/master/article05/19_sim­plest_for_loop.go
20 20_basic_for_loop.go druhá forma nekonečné smyčky https://github.com/tisnik/go-fedora/blob/master/article05/20_ba­sic_for_loop.go
21 21_for_loop_with_condition.go smyčka s testem na začátku https://github.com/tisnik/go-fedora/blob/master/article05/21_for_lo­op_with_condition.go
22 22_c_like_loop.go céčkovská varianta smyčky https://github.com/tisnik/go-fedora/blob/master/article05/22_c_li­ke_loop.go
23 23_better_for.go vylepšení předchozího příkladu https://github.com/tisnik/go-fedora/blob/master/article05/23_bet­ter_for.go
24 24_better_for_local_variable.go lokální řídicí proměnná smyčky https://github.com/tisnik/go-fedora/blob/master/article05/24_bet­ter_for_local_variable.go
25 25_for_range.go programová smyčka for-range https://github.com/tisnik/go-fedora/blob/master/article05/25_for_ran­ge.go
26 26_for_range_map.go iterace přes hodnoty uložené do mapy https://github.com/tisnik/go-fedora/blob/master/article05/26_for_ran­ge_map.go
27 27_break_statement.go použití příkazu break https://github.com/tisnik/go-fedora/blob/master/article05/27_bre­ak_statement.go
28 28_continue_statement.go použití příkazu continue https://github.com/tisnik/go-fedora/blob/master/article05/28_con­tinue_statement.go
29 29_nested_loops.go vnořené smyčky https://github.com/tisnik/go-fedora/blob/master/article05/29_nes­ted_loops.go
30 30_break_from_inner_loop.go příkaz break s návěštím https://github.com/tisnik/go-fedora/blob/master/article05/30_bre­ak_from_inner_loop.go
31 31_continue_from_inner_loop.go příkaz continue s návěštím https://github.com/tisnik/go-fedora/blob/master/article05/31_con­tinue_from_inner_loop.go

20. Odkazy na Internetu

  1. The Go Programming Language (home page)
    https://golang.org/
  2. GoDoc
    https://godoc.org/
  3. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  4. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  5. The Go Programming Language Specification
    https://golang.org/ref/spec
  6. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  7. Package builtin
    https://golang.org/pkg/builtin/
  8. Package fmt
    https://golang.org/pkg/fmt/
  9. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  10. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  11. Learning Go
    https://www.miek.nl/go/
  12. Go Bootcamp
    http://www.golangbootcamp.com/
  13. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  14. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  15. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  16. The Go Blog
    https://blog.golang.org/
  17. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  18. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  19. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  20. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  21. 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
  22. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  23. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  24. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  25. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  26. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  27. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  28. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  29. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  30. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  31. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  32. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  33. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  34. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  35. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  36. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  37. 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/
  38. 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
  39. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  40. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  41. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  42. Go vs. Python
    https://www.peterbe.com/plog/govspy
  43. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  44. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  45. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  46. Go by Example: Slices
    https://gobyexample.com/slices
  47. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  48. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  49. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  50. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  51. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  52. nils In Go
    https://go101.org/article/nil.html
  53. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  54. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  55. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  56. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  57. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  58. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  59. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  60. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  61. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  62. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  63. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  64. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  65. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  66. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  67. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  68. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  69. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  70. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  71. 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
  72. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  73. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  74. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  75. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  76. Selectors
    https://golang.org/ref/spec#Selectors
  77. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  78. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  79. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  80. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  81. Part 21: Goroutines
    https://golangbot.com/goroutines/
  82. Part 22: Channels
    https://golangbot.com/channels/
  83. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  84. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  85. 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/
  86. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  87. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  88. Control Structures
    https://www.golang-book.com/books/intro/5
  89. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  90. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  91. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  92. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  93. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  94. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
Našli jste v článku chybu?