Obsah
3. Logické (Booleovské) operátory
4. Logické operátory a typové kontroly jazyka Go
5. Operace pro zvýšení a snížení hodnoty o jedničku
6. Nepodporovaná použití operátorů ++ a –
7. Klíčové slovo select: posílání zpráv přes kanály
8. Použití konstrukce select-case pro čekání na data z gorutin
9. Význam větve default v konstrukci select-case
10. Použití konstrukce select-case při posílání dat
12. Kombinace čtení a zápisu v konstrukci select-case
13. Deklarace konstant v programovacím jazyku Go
14. Demonstrační příklad: konstanty různých typů, blok s konstantami
15. Automatické generování celočíselné řady pomocí klíčového slova iota
16. Praktické použití klíčového slova iota
17. Funkce s variabilním počtem parametrů v programovacím jazyku Go
18. Základní informace o adresářové struktuře při práci se složitějšími projekty
19. Repositář s demonstračními příklady
1. Dokončení popisu operátorů
V úvodní části dnešního článku navážeme na článek předchozí, v němž jsme se kromě dalších věcí zabývali i operátory, které je možné v programovacím jazyku Go použít. Již jsme si popsali standardní aritmetické operátory, relační operátory (a jejich omezení) a taktéž bitové posuny. Musíme si ovšem popsat i bitové operátory a mj. i operace určené pro zvýšení či snížení číselné hodnoty o jedničku (což ovšem nejsou plnohodnotné operátory, jak ostatně uvidíme v dalším textu).
Jen pro připomenutí si znovu ukažme tabulku všech operátorů, které je možné použít v programovacím jazyce Go:
aritmetické | + | – | * | / | % | |
aritmetické s přiřazením | += | -= | *= | /= | %= | |
logické | && | || | ! | |||
posuny a bitové operace | << | >> | & | | | ^ | &^ |
posuny a bitové operace s přiřazením | <<= | >>= | &= | |= | ^= | &^= |
relační | == | != | < | <= | > | >= |
operace s adresami | * | & | ||||
unární operátory | + | – | ^ | |||
další operátory | <- | := |
2. Bitové operátory
V této kapitole se budeme zabývat bitovými operacemi, které jsou v programovacím jazyce Go realizovány s využitím pětice operátorů. Tyto operátory jsou vypsány v následující tabulce:
Operátor | Kombinace s přiřazením | Význam |
---|---|---|
^ | negace bit po bitu (podobně jako operátor ~ v C) | |
& | &= | logický součin prováděný bit po bitu |
| | |= | logický součet prováděný bit po bitu |
^ | ^= | logická nonekvivalence prováděná bit po bitu |
&^ | &^= | maskování bitů vybraných zadanou maskou (operace AND NOT) |
První operátor je unární, tj. aplikuje se pouze na jediný operand, který je zapsán za operátor, podobně jako u dalších unárních operátorů (operace ++ a – jsou výjimkou, ale v tomto případě se nejedná o plnohodnotné operátory). Tento operátor slouží pro negaci všech bitů v celočíselné hodnotě. Operátor lze aplikovat jak na hodnoty bez znaménka (unsigned), tak i na hodnoty se znaménkem (signed), takže můžeme například napsat:
var b uint8 = 0 var x uint = 1 var y uint = 2 var z int = 1 var w int = 2 fmt.Printf("%x\n", ^b) fmt.Printf("%x\n", ^x) fmt.Printf("%x\n", ^y) fmt.Printf("%x\n", ^z) fmt.Printf("%x\n", ^w)
S výsledky:
ff fffffffffffffffe fffffffffffffffd -2 -3
Další trojice operátorů zapisovaných znaky &, | a ^ patří mezi binární operátory, které jsou aplikovány na dvojici operandů. Opět se může jednat jak o hodnoty se znaménkem, tak i o hodnoty bez znaménka. Význam těchto operátorů odpovídá podobně zapisovaným operátorům v C, C++ či Javě (viz též demonstrační příklad uvedený v závěru kapitoly). Zajímavější a ve vyšších programovacích jazycích pravděpodobně i unikátní je však poslední z bitových operátorů, který se zapisuje dvojicí znaků &^. Tento operátor provádí logický součin bit po bitu, ovšem všechny bity druhého operandu jsou nejdříve znegovány. Operátor &^ se tedy používá pro maskování, kdy například budeme potřebovat vynulovat bit 1 pomocí operace x &^ 1 atd.:
var b uint8 = 0xff fmt.Printf("%x\n", b) fmt.Printf("%x\n", b &^ 1) fmt.Printf("%x\n", b &^ 2) fmt.Printf("%x\n", b &^ 0x0f)
S výsledky:
ff fe fd f0
Všechny binární bitové operátory jsou představeny v dnešním prvním demonstračním příkladu, v němž jsou aplikovány na dvě celočíselné proměnné:
package main import "fmt" func main() { x := 1 y := 0xfe fmt.Printf("%x & %x == %x\n", x, y, x&y) fmt.Printf("%x &^ %x == %x\n", x, y, x&^y) fmt.Printf("%x | %x == %x\n", x, y, x|y) fmt.Printf("%x ^ %x == %x\n", x, y, x^y) x ^= y fmt.Printf("new x = %x\n", x) x |= y fmt.Printf("new x = %x\n", x) x ^= y fmt.Printf("new x = %x\n", x) x &^= 0x01 fmt.Printf("new x = %x\n", x) fmt.Println() x = 1 y = 2 fmt.Printf("%x & %x == %x\n", x, y, x&y) fmt.Printf("%x &^ %x == %x\n", x, y, x&^y) fmt.Printf("%x | %x == %x\n", x, y, x|y) fmt.Printf("%x ^ %x == %x\n", x, y, x^y) }
Po spuštění tohoto příkladu získáme tyto výsledky:
1 & fe == 0 1 &^ fe == 1 1 | fe == ff 1 ^ fe == ff new x = ff new x = ff new x = 1 new x = 0 1 & 2 == 0 1 &^ 2 == 1 1 | 2 == 3 1 ^ 2 == 3
3. Logické (Booleovské) operátory
Pro úplnost si ještě zopakujme, že v programovacím jazyku Go nalezneme tři operátory, které jsou aplikovatelné na logické (pravdivostní) hodnoty true a false. Jedná se o logickou negaci, logický součin a logický součet. Tyto tři operátory se zapisují následujícím způsobem (u binárních operátorů existuje i možnost jejich kombinace s přiřazením):
Operátor | Kombinace s přiřazením | Význam |
---|---|---|
! | negace pravdivostní hodnoty | |
&& | &&= | logický součin dvou pravdivostních hodnot |
|| | ||= | logický součet dvou pravdivostních hodnot |
Opět se podívejme na jednoduchý demonstrační příklad, v němž jsou všechny tři výše zmíněné operátory použity:
package main import "fmt" func main() { x := true y := false fmt.Printf("!%v == %v\n", x, !x) fmt.Printf("!%v == %v\n", y, !y) fmt.Printf("%v && %v == %v\n", x, y, x && y) fmt.Printf("%v || %v == %v\n", x, y, x || y) fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && false) fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && true) }
Po překladu a spuštění tohoto příkladu získáme tyto výsledky:
!true == false !false == true true && false == false true || false == true true && false || true && false == false true && false || true && false == true
V Go nelze použít varianty & a | s nezkráceným vyhodnocením výrazu. Operátory && a || vždy používají zkrácené vyhodnocování (short circuit), což konkrétně znamená, že pokud je po vyhodnocení prvního operandu zřejmé, jaký bude výsledek operace, druhý operand se nevyhodnocuje. To má význam v případě, že se při vyhodnocování volají funkce, popř. pokud se například dělí nulou atd.:
package main import "fmt" func f1() bool { println("f1") return true } func f2() bool { println("f2") return false } func f3() bool { println("f2") return false } func main() { fmt.Printf("short circuit &&: %v\n", f1() && f2()) fmt.Printf("short circuit ||: %v\n", f1() || f2()) fmt.Printf("short circuit &&: %v\n", f2() && f3()) fmt.Printf("short circuit ||: %v\n", f2() || f3()) }
Při pohledu na výsledky předchozího demonstračního příkladu se skutečně můžeme přesvědčit, že se druhá funkce ve výrazu v některých případech vůbec nevolá:
f1 f2 short circuit &&: false f1 short circuit ||: true f2 short circuit &&: false f2 f2 short circuit ||: false
4. Logické operátory a typové kontroly jazyka Go
Žádné další operace nejsou s pravdivostními (logickými) hodnotami dovoleny! To například znamená, že následující demonstrační příklad nebude možné přeložit a pochopitelně ani spustit:
package main import "fmt" func main() { x := true y := false fmt.Printf("^%v == %v\n", x, ^x) fmt.Printf("^%v == %v\n", y, ^y) fmt.Printf("%v & %v == %v\n", x, y, x & y) fmt.Printf("%v | %v == %v\n", x, y, x | y) fmt.Printf("%v + %v == %v\n", x, y, x + y) fmt.Printf("%v - %v == %v\n", x, y, x - y) }
Při pokusu o překlad vypíše překladač tato chybová hlášení:
./02B_boolean_operators_improper_usage.go:16:31: invalid operation: ^ bool ./02B_boolean_operators_improper_usage.go:17:31: invalid operation: ^ bool ./02B_boolean_operators_improper_usage.go:19:40: invalid operation: x & y (operator & not defined on bool) ./02B_boolean_operators_improper_usage.go:20:40: invalid operation: x | y (operator | not defined on bool) ./02B_boolean_operators_improper_usage.go:21:40: invalid operation: x + y (operator + not defined on bool) ./02B_boolean_operators_improper_usage.go:22:40: invalid operation: x - y (operator - not defined on bool)
Samozřejmě to platí i naopak – logické operátory nejsou aplikovatelné na celá čísla, čísla s plovoucí řádovou čárkou apod. Ani tento program tedy není v žádném případě korektní:
package main import "fmt" func main() { x := 1 y := 2 fmt.Printf("!%v == %v\n", x, !x) fmt.Printf("!%v == %v\n", y, !y) fmt.Printf("%v && %v == %v\n", x, y, x && y) fmt.Printf("%v || %v == %v\n", x, y, x || y) fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && false) fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && true) }
Takto krátký program obsahuje až překvapivě mnoho chyb:
./02C_boolean_operators_improper_usage.go:16:31: invalid operation: ! int ./02C_boolean_operators_improper_usage.go:17:31: invalid operation: ! int ./02C_boolean_operators_improper_usage.go:19:41: invalid operation: x && y (operator && not defined on int) ./02C_boolean_operators_improper_usage.go:20:41: invalid operation: x || y (operator || not defined on int) ./02C_boolean_operators_improper_usage.go:22:66: invalid operation: x && y (operator && not defined on int) ./02C_boolean_operators_improper_usage.go:22:76: cannot convert false (type untyped bool) to type int ./02C_boolean_operators_improper_usage.go:22:76: invalid operation: x && false (mismatched types int and bool) ./02C_boolean_operators_improper_usage.go:23:66: invalid operation: x && y (operator && not defined on int) ./02C_boolean_operators_improper_usage.go:23:76: cannot convert true (type untyped bool) to type int ./02C_boolean_operators_improper_usage.go:23:76: invalid operation: x && true (mismatched types int and bool)
5. Operace pro zvýšení a snížení hodnoty o jedničku
Programovací jazyk Go umožňuje jednoduché zvýšení či snížení nějaké číselné hodnoty (celého čísla, čísla s plovoucí řádovou čárkou, dokonce i čísla komplexního). Tyto operace jsou zapisovány znaky ++ a --, které musíme vždy zapsat ZA operand. Na rozdíl od prakticky všech céčkovských programovacích jazyků ovšem není povoleno použít tyto znaky před operandem. Navíc – což je ještě více omezující podmínka – musí být tyto operace zapsány jako příkaz (statement), nikoli jako součást složitějšího výrazu (expression). Z těchto důvodů se operace ++ a – většinou ani neuvádí v seznamu operátorů, protože tvoří samostatnou syntaktickou kategorii. Nejjednodušší příklad použití operací ++ a – může vypadat následovně:
x := 1 x++
popř. pochopitelně můžeme celočíselnou hodnotu o jedničku snížit:
x := 1 x--
Zajímavé a užitečné je chování operací ++ a – ve chvíli, kdy máme k dispozici ukazatel na proměnnou (resp. obecněji ukazatel na hodnotu). V takovém případě můžeme napsat:
x := 1 px := &x *px++
Na posledním řádku se zvýší hodnota proměnné x, protože jazyk Go má odlišnou tabulku priorit operátorů. V programovacím jazyku C má ovšem stejně zapsaný příkaz odlišný význam, protože nejprve přečte hodnotu z adresy obsažené v px a poté zvýší hodnotu px (tedy adresy) o jedničku (to bude v našem případě potenciálně nekorektní operace, protože v px je adresa proměnné a nikoli například adresa prvku pole).
#include <stdio.h> int main(void) { int x = 1; int *px = &x; *px++; printf("%d\n", x); return 0; }
Můžeme si vyzkoušet, že předchozí céčkovský příklad se skutečně chová odlišně:
$ gcc px.c $ ./a.out 1
Stejné chování, jakého jsme dosáhli v Go, musíme naprogramovat s využitím závorek pro změnu priorit operátorů:
#include <stdio.h> int main(void) { int x = 1; int *px = &x; (*px)++; printf("%d\n", x); return 0; }
Otestování chování upraveného céčkovského programu:
$ gcc px.c $ ./a.out 2
Vraťme se nyní zpět k popisu programovacího jazyka Go. Operace ++ a – je možné použít i pro hodnoty typu complex64 a complex128. V tomto případě se operace týkají reálné složky komplexních čísel, imaginární složka zůstane nezměněna:
z := 1 + 2i z++
V dalším demonstračním příkladu, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article07/03_inc_dec.go, jsou ukázány některé možnosti korektního použití operací ++ a --:
package main import "fmt" func main() { x := 1 fmt.Printf("x = %d\n", x) x++ fmt.Printf("x = %d\n", x) x-- fmt.Printf("x = %d\n", x) px := &x fmt.Printf("x = %d\n", *px) *px++ fmt.Printf("x = %d\n", *px) *px-- fmt.Printf("x = %d\n", *px) y := 3.14 fmt.Printf("y = %f\n", y) y++ fmt.Printf("y = %f\n", y) z := 1 + 2i fmt.Printf("z = %f\n", z) z++ fmt.Printf("z = %f\n", z) }
Po překladu a spuštění tohoto příkladu by se na standardním výstupu měly objevit tyto řádky:
x = 1 x = 2 x = 1 x = 1 x = 2 x = 1 y = 3.140000 y = 4.140000 z = (1.000000+2.000000i) z = (2.000000+2.000000i)
6. Nepodporovaná použití operátorů ++ a --
V předchozí kapitole jsme si řekli, že možnosti použití operací ++ a – jsou v programovacím jazyku Go omezenější, než je tomu v klasických jazycích odvozených od céčka. V první řadě není dovoleno zapsat tyto operace před operandy. Na toto nekorektní použití nás upozorní překladač:
package main import "fmt" func main() { x := 1 fmt.Printf("x = %d\n", x) ++x fmt.Printf("x = %d\n", x) --x fmt.Printf("x = %d\n", x) }
Při pokusu o překlad tohoto demonstračního příkladu se vypíše chyba u první operace ++x
./04_inc_dec_bad_usage.go:16:2: syntax error: unexpected ++, expecting }
Dále je nutné ++ a – použít jen v samostatném příkazu, nikoli ve výrazu. Ani následující příklad tedy není pro překladač programovacího jazyka Go korektní:
package main import "fmt" func main() { x := 1 fmt.Printf("x = %d\n", x) fmt.Printf("x = %d\n", x++) fmt.Printf("x = %d\n", x--) }
Překladač nyní vypíše chyby u obou operací ++ i --:
./05_inc_dec_bad_usage.go:16:26: syntax error: unexpected ++, expecting comma or ) ./05_inc_dec_bad_usage.go:18:26: syntax error: unexpected --, expecting comma or )
Naproti tomu v počítané variantě programové smyčky for operace ++ a – použít můžeme, protože se na příslušném místě očekává iterační příkaz:
package main func main() { var i int for i = 0; i < 10; i++ { println(i) } println() println(i) }
7. Klíčové slovo select: posílání zpráv přes kanály
Jedno z posledních klíčových slov programovacího jazyka Go, s nímž jsme se doposud nesetkali, je slovo select. Toto klíčové slovo se používá společně s větvemi case a popř. i s větví default pro vytvoření programové konstrukce určené pro příjem dat popř. i pro vysílání dat do gorutin s využitím kanálů. V některých aplikacích totiž nemůžeme čekat na data posílaná do jediného kanálů tak, jak je to naznačeno v následujícím kódu:
func main() { channel := make(chan int) go message(1, channel) code, status := <-channel fmt.Printf("received code: %d and status: %t\n", code, status) fmt.Println("main end") }
Namísto toho je někdy zapotřebí zapsat zhruba následující kód:
func main() { channel1 := make(chan int) channel2 := make(chan int) go message(1, channel1) go message(2, channel2) go message(3, channel3) code, status := <-channel1 NEBO code, status := <-channel2 NEBO code, status := <-channel3 fmt.Printf("received code: %d and status: %t\n", code, status) fmt.Println("main end") }
A právě zvýrazněná část kódu je zapisována s využitím slova select.
Toto slovo se ovšem může použít i v opačném významu, tj. při posílání dat, tedy přibližně v tomto významu:
func main() { channel1 := make(chan int) channel2 := make(chan int) go message(1, channel1) go message(2, channel2) go message(3, channel3) channel1 <- data1 NEBO channel2 <- data2 NEBO channel3 <- data3 fmt.Printf("received code: %d and status: %t\n", code, status) fmt.Println("main end") }
8. Použití konstrukce select-case pro čekání na data z gorutin
Ukažme si nyní velmi jednoduché použití konstrukce select-case při čekání na data, která mohou být poslána do jednoho ze dvou kanálů ch1 a ch2. V tomto programu jsou nejdříve vytvořeny dva kanály s kapacitou 1 (jediné celé číslo):
ch1 := make(chan int) ch2 := make(chan int)
Následně jsou spuštěny dvě gorutiny, z nichž první pošle data do prvního kanálu ch1 a druhá do druhého kanálu ch2:
go worker(ch1, 1) go worker(ch2, 2)
Nakonec V konstrukci select-case počkáme na to, až jsou data dostupná v libovolném z těchto kanálů (teoreticky ani nemusíme jeden z workerů-gorutin spouštět):
select { case <-ch1: fmt.Println("Data z kanálu 1") case <-ch2: fmt.Println("Data z kanálu 2") }
Ve chvíli, kdy jsou data přijata, je konstrukce select-case ukončena a ihned poté se ukončí i běh celé aplikace.
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article07/06_select_statement_receive.go:
package main import ( "fmt" "time" ) func worker(channel chan int, worker int) { fmt.Printf("Worker %d spuštěn\n", worker) time.Sleep(2 * time.Second) channel <- 1 fmt.Printf("Worker %d ukončen\n", worker) } func main() { ch1 := make(chan int) ch2 := make(chan int) go worker(ch1, 1) go worker(ch2, 2) select { case <-ch1: fmt.Println("Data z kanálu 1") case <-ch2: fmt.Println("Data z kanálu 2") } }
Po spuštění tohoto příkladu můžeme vidět, že se obě gorutiny skutečně spustily, přičemž se v příkazu select náhodně vybrala druhá větev case, v níž se přečetla data z druhého kanálu, takže se příslušný worker stihl ukončit (což ovšem není nijak zaručené chování):
Worker 2 spuštěn Worker 1 spuštěn Worker 2 ukončen Data z kanálu 2
9. Význam větve default v konstrukci select-case
V některých aplikacích může být výhodné, aby se v případě, že žádný kanál zpracovávaný v konstrukci select-case neobsahuje data, pokračovalo v nějakém dalším výpočtu. K tomuto účelu slouží větev default zavolaná ve chvíli, kdy není možné zpracovat ani jednu z větví case. Příklad použití této nové větve je ukázán na dalším demonstračním příkladu:
package main import ( "fmt" "time" ) func worker(channel chan int, worker int) { fmt.Printf("Worker %d spuštěn\n", worker) time.Sleep(2 * time.Second) channel <- 1 fmt.Printf("Worker %d ukončen\n", worker) } func main() { ch1 := make(chan int) ch2 := make(chan int) go worker(ch1, 1) go worker(ch2, 2) for true { select { case <-ch1: fmt.Println("Data z kanálu 1") case <-ch2: fmt.Println("Data z kanálu 2") default: fmt.Println("Žádná data nejsou k dispozici") } time.Sleep(1 * time.Second) } }
V příkladu je implementována nekonečná smyčka a v konstrukci select-case je umístěna i větev defalt zavolaná ve chvíli, kdy nejsou k dispozici data v žádném kontrolovaném kanálu. V tomto případě není select-case blokující, jak ostatně můžeme vidět i z výpisů:
Worker 1 spuštěn Žádná data nejsou k dispozici Worker 2 spuštěn Žádná data nejsou k dispozici Data z kanálu 1 Worker 1 ukončen Data z kanálu 2 Worker 2 ukončen Žádná data nejsou k dispozici Žádná data nejsou k dispozici Žádná data nejsou k dispozici ... ... ...
10. Použití konstrukce select-case při posílání dat
Programovou konstrukci typu select-case je možné využít nejenom pro příjem dat (s případným čekáním na okamžik, až se data objeví v kanálu), ale i pro posílání dat. Význam je přitom stejný – obě operace mohou být blokující a mnohdy potřebujeme, aby jedna gorutina posílala data většímu množství gorutin, samozřejmě opět s využitím kanálů. V následujícím demonstračním příkladu budeme posílat náhodná data (buď nulu nebo jedničku) do gorutiny reprezentované funkcí worker:
package main import "fmt" func worker(channel chan int) { for true { value, ok := <-channel if ok { fmt.Printf("Přijata hodnota %d\n", value) } else { fmt.Printf("Kanál je uzavřen\n") } } } func main() { ch1 := make(chan int) go worker(ch1) for i := 0; i < 10; i++ { select { case ch1 <- 0: fmt.Println("Poslána nula") case ch1 <- 1: fmt.Println("Poslána jednička") } } }
Příklad chování tohoto příkladu po jeho spuštění:
Přijata hodnota 0 Poslána nula Poslána jednička Přijata hodnota 1 Přijata hodnota 0 Poslána nula Poslána jednička Přijata hodnota 1 Přijata hodnota 0 Poslána nula Poslána nula Přijata hodnota 0 Přijata hodnota 1 Poslána jednička Poslána jednička Přijata hodnota 1 Přijata hodnota 0 Poslána nula Poslána jednička
11. Blokující zápis do kanálu
Předchozí příklad je možné rozšířit o druhou gorutinu-workera. Oba workeři nyní budou sdílet jediný komunikační kanál a při zápisu hodnoty do kanálu je tedy náhodně vybrán ten worker, který je v daný okamžik k dispozici. První varianta příkladu používá kanál s kapacitou jediné položky (jedná se tedy vlastně o jednoduchý mailbox):
package main import ( "fmt" "time" ) func worker(channel chan int, worker int) { for true { value, ok := <-channel if ok { fmt.Printf("Worker %d přijal hodnotu %d\n", worker, value) } else { fmt.Printf("Kanál je uzavřen pro workera %d\n", worker) } time.Sleep(1 * time.Second) } } func main() { ch1 := make(chan int) go worker(ch1, 1) go worker(ch1, 2) for i := 0; i < 10; i++ { select { case ch1 <- 0: fmt.Println("Poslána nula") case ch1 <- 1: fmt.Println("Poslána jednička") } } }
Zápis do kanálu je v tomto případě blokující operace a opět se náhodně vybere jedna z větví v konstrukci select-case:
Worker 2 přijal hodnotu 1 Poslána jednička Poslána nula Worker 1 přijal hodnotu 0 Worker 2 přijal hodnotu 0 Poslána nula Poslána nula Worker 1 přijal hodnotu 0 Worker 2 přijal hodnotu 0 Poslána nula Worker 1 přijal hodnotu 0 Poslána nula Worker 1 přijal hodnotu 0 Poslána nula Poslána jednička Worker 2 přijal hodnotu 1 Worker 1 přijal hodnotu 1 Poslána jednička Poslána nula
Pokud ovšem v příkladu změníme řádek, na němž se vytváří kanál, z:
ch1 := make(chan int)
na:
ch1 := make(chan int, 20)
stane se zápis neblokující operací, protože kanál bude mít kapacitu dvaceti položek (jedná se o frontu) a my do něj zapíšeme pouze deset položek. Chování aplikace se změní:
Poslána nula Poslána nula Poslána nula Poslána nula Poslána jednička Poslána nula Poslána jednička Poslána jednička Poslána nula Poslána jednička
Další text se (většinou) nevypíše, a to z toho prostého důvodu, že hlavní gorutina skončí, aniž by došlo k přepnutí kontextu do prvního či druhého workera.
12. Kombinace čtení a zápisu v konstrukci select-case
V posledním demonstračním příkladu, v němž použijeme konstrukci select-case, zkombinujeme jak čtení, tak i zápis do různých kanálů. I tuto kombinaci je možné v jazyku Go použít, takže následující zápis je zcela korektní, a to jak syntakticky, tak i sémanticky:
select { case ch1 <- 0: fmt.Println("Poslána nula") case ch1 <- 1: fmt.Println("Poslána jednička") case data, ok := <-ch2: if ok { fmt.Printf("Přijata data %d z kanálu 2\n", data) } case data, ok := <-ch3: if ok { fmt.Printf("Přijata data %d z kanálu 3\n", data) } }
V předchozí konstrukci se vybere vždy pouze jediná větev v závislosti na tom, zda jsou v prvních dvou kanálech data, popř. zda jsou druhé dva kanály prázdné či obsazené.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
package main import ( "fmt" "time" ) func receiver(channel chan int, receiver int) { for true { value, ok := <-channel if ok { fmt.Printf("Příjemce %d přijal hodnotu %d\n", receiver, value) } else { fmt.Printf("Kanál je pro příjemce %d uzavřen\n", receiver) } time.Sleep(2 * time.Second) } } func sender(channel chan int, sender int) { fmt.Printf("Odesílatel %d byl spuštěn\n", sender) for i := 1; i <= 5; i++ { time.Sleep(1 * time.Second) channel <- i } fmt.Printf("Odesílatel %d byl ukončen\n", sender) } func main() { ch1 := make(chan int) ch2 := make(chan int) ch3 := make(chan int) // ch1 := make(chan int, 20) go receiver(ch1, 1) go receiver(ch1, 2) go sender(ch2, 1) go sender(ch3, 2) for i := 0; i < 20; i++ { select { case ch1 <- 0: fmt.Println("Poslána nula") case ch1 <- 1: fmt.Println("Poslána jednička") case data, ok := <-ch2: if ok { fmt.Printf("Přijata data %d z kanálu 2\n", data) } case data, ok := <-ch3: if ok { fmt.Printf("Přijata data %d z kanálu 3\n", data) } } } }
Příklad výsledků vypsaných tímto příkladem po jeho spuštění:
Odesílatel 1 byl spuštěn Odesílatel 2 byl spuštěn Poslána nula Poslána jednička Příjemce 1 přijal hodnotu 1 Příjemce 2 přijal hodnotu 0 Přijata data 1 z kanálu 2 Přijata data 1 z kanálu 3 Příjemce 2 přijal hodnotu 1 Poslána jednička Přijata data 2 z kanálu 2 Poslána jednička Příjemce 1 přijal hodnotu 1 Přijata data 2 z kanálu 3 Přijata data 3 z kanálu 2 Přijata data 3 z kanálu 3 Příjemce 2 přijal hodnotu 1 Poslána jednička Poslána nula Příjemce 1 přijal hodnotu 0 Přijata data 4 z kanálu 3 Přijata data 4 z kanálu 2 Odesílatel 2 byl ukončen Přijata data 5 z kanálu 3 Přijata data 5 z kanálu 2 Odesílatel 1 byl ukončen Příjemce 2 přijal hodnotu 0 Poslána nula Příjemce 1 přijal hodnotu 1 Poslána jednička Příjemce 2 přijal hodnotu 0 Poslána nula Příjemce 1 přijal hodnotu 0 Poslána nula
13. Deklarace konstant v programovacím jazyku Go
V programovacím jazyku Go je možné deklarovat konstanty s využitím klíčového slova const, za nímž se zapíše jméno konstanty, její typ a hodnota konstanty (oddělená přiřazovacím operátorem =). V případě, že se nespecifikuje typ konstanty, bude odvozen automaticky překladačem na základě přiřazované hodnoty. Příklad deklarací konstant různých typů (povšimněte si, že již definované konstanty mohou být součástí dalších konstantních výrazů):
package main import "fmt" const Pi float64 = 3.1415927 const E = 2.71828 const z0 int = 0 const z1 = 0 const z2 = z0 + z1 func main() { fmt.Printf("Pi = %f\n", Pi) fmt.Printf("e = %f\n", E) fmt.Printf("z0 = %d\n", z0) fmt.Printf("z1 = %d\n", z1) fmt.Printf("z2 = %d\n", z2) }
Výsledek po spuštění tohoto příkladu:
Pi = 3.141593 e = 2.718280 z0 = 0 z1 = 0 z2 = 0
14. Demonstrační příklad: konstanty různých typů, blok s konstantami
Poměrně často se setkáme i s uzavřením všech konstant do jediného bloku začínajícího klíčovým slovem const. Před jednotlivými konstantami se potom toto slovo již nezapisuje, což je ostatně patrné i při pohledu na další příklad:
package main import "fmt" const ( Pi float64 = 3.1415927 E = 2.71828 z0 int = 0 z1 = 0 z2 = z0 + z1 ) func main() { fmt.Printf("Pi = %f\n", Pi) fmt.Printf("e = %f\n", E) fmt.Printf("z0 = %d\n", z0) fmt.Printf("z1 = %d\n", z1) fmt.Printf("z2 = %d\n", z2) }
Výsledek, který získáme po spuštění tohoto příkladu:
Pi = 3.141593 e = 2.718280 z0 = 0 z1 = 0 z2 = 0
15. Automatické generování celočíselné řady pomocí klíčového slova iota
S blokem s konstantami, který jsme si ukázali v předchozí kapitole, souvisí i speciální identifikátor iota. Jedná se o konstantu, která má při prvním použití hodnotu 0, při druhém použití hodnotu 1 atd. Jinými slovy je tento identifikátor interně realizován jako celočíselný čítač v překladači, jehož hodnota se nastavuje na nulu při každém vstupu do bloku const. Podívejme se na následující demonstrační příklad, v němž se speciální identifikátor iota používá pro realizaci (v Go neexistujícího) výčtového typu:
package main import "fmt" const ( Pondeli = iota Utery = iota Streda = iota Ctvrtek = iota Patek = iota Sobota = iota Nedele = iota ) func main() { fmt.Printf("%d\n", Pondeli) fmt.Printf("%d\n", Streda) fmt.Printf("%d\n", Patek) }
Po překladu a spuštění tohoto příkladu získáme tři hodnoty odpovídající dnům pondělí, středa a pátek:
0 2 4
16. Praktické použití klíčového slova iota
Při praktickém použití klíčového slova iota se většinou tento identifikátor zapisuje pouze za první jméno konstanty v bloku const. U dalších konstant již není zapotřebí zapisovat ani typ, ani do nich explicitně přiřazovat hodnotu. Pokud není přiřazení zapsáno, automaticky se v průběhu překladu doplní text „= iota“, což je ukázáno na dalším příkladu:
package main import "fmt" const ( Pondeli = iota Utery Streda Ctvrtek Patek Sobota Nedele ) func main() { fmt.Printf("%d\n", Pondeli) fmt.Printf("%d\n", Streda) fmt.Printf("%d\n", Patek) }
Výsledek bude stejný jako v předchozím příkladu:
0 2 4
Ve skutečnosti nám ovšem nic nebrání použít iota i poněkud netradičně v prakticky libovolných konstantních výrazech (proto je iota obecnějším konceptem):
package main import "fmt" const ( a = iota + 0.5i b = iota + 0.5i c = 1.0 / iota d = 1 << iota e = iota ^ 0xff ) func main() { fmt.Printf("%f\n", a) fmt.Printf("%f\n", b) fmt.Printf("%f\n", c) fmt.Printf("%d\n", d) fmt.Printf("%d\n", e) }
(0.000000+0.500000i) (1.000000+0.500000i) 0.500000 8 251
17. Funkce s variabilním počtem parametrů v programovacím jazyku Go
Poslední vlastností programovacího jazyka Go, s níž se dnes seznámíme, je koncept variadických funkcí, tj. funkcí s variabilním počtem parametrů. Tyto parametry jsou ovšem typované (nelze použít obdobu void *). Pokud funkce obsahuje jak běžné parametry, tak i nepovinné parametry, musí být běžné parametry umístěny před parametry nepovinné, tj. tak, jak je to ukázáno v dalším příkladu:
package main import "fmt" func f1(msg string) { fmt.Printf("%s\n", msg) } func f2(parts ...string) { for _, val := range parts { fmt.Printf("%s ", val) } fmt.Println() } func f3(prefix string, parts ...string) { fmt.Println(prefix) for _, val := range parts { fmt.Printf("%s ", val) } fmt.Println() } func main() { f1("Hello") f2("Hello", "world", "!") f3("Message:", "Hello", "world", "again", "!") }
Funkce f1 akceptuje jeden parametr, funkce f2 libovolný počet parametrů (i žádný parametr) a funkce f3 akceptuje minimálně jeden parametr. Všechny parametry jsou typu řetězec:
Hello Hello world ! Message: Hello world again !
Nepovinné parametry ovšem mohou být uvedeny jen na konci seznamu parametrů, takže například tento příklad není korektní a nebude ho možné přeložit:
package main import "fmt" func f4(prefix string, parts1 ...string, parts2 ...string) { fmt.Println(prefix) for _, val := range parts1 { fmt.Printf("%s ", val) } fmt.Println() for _, val := range parts2 { fmt.Printf("%s ", val) } fmt.Println() } func main() { f4("Message:", "Hello", "world", "again", "!") }
Při pokusu o překlad se vypíše:
./17_variadic_function_improper_usage.go:12:34: can only use ... with final parameter in list
18. Základní informace o adresářové struktuře při práci se složitějšími projekty
Již v úvodním článku jsme si řekli, že při instalaci jazyka Go je vhodné nastavit proměnnou GOPATH, například na adresář ~/home/go. Ve skutečnosti je problematika proměnné prostředí GOPATH a jejího významu poněkud složitější, protože tato proměnná by měla obsahovat absolutní (!) cestu k adresáři, který má tuto strukturu:
. ├── bin │ ├── │ └── ├── pkg │ ├── │ └── └── src ├── ├── └──
Celý adresář se nazývá pracovní plocha (workspace) a obsahuje tři podadresáře pojmenované src, pkg a bin. Prozatím nás bude zajímat především podadresář src, v němž jsou typicky uloženy repositáře (repository) a v každém repositáři je libovolný počet balíčků (package). V balíčcích už nalezneme zdrojové soubory .go, pomocné skripty, datové soubory, dokumentaci atd. Workspace představovaný adresářem nazvaným go, v němž jsou umístěny tři repositáře, může vypadat následovně:
. └── go ├── bin ├── pkg └── src ├── repository1 │ ├── hello1 │ │ └── hello.go │ └── hello2 │ └── hello.go ├── repository2 └── repository3
Vidíme, že v repositáři pojmenovaném repository1 jsou umístěny dva balíčky s názvy hello1 a hello2, přičemž v každém balíčku je jediný zdrojový soubor, který je (čistě náhodou) shodně pojmenován hello.go.
package main func main() { println("repository1: Hello world!") }
Další informace o této adresářové struktuře a jejím významu si podrobněji popíšeme v navazujícím článku, v němž se budeme věnovat správě balíčků, jejich instalaci apod.
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
- Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - 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/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation