Hlavní navigace

Datové typy v programovacím jazyku Go

Pavel Tišnovský

Ve druhé části seriálu o jazyku Go se seznámíme s typovým systémem tohoto jazyka. Kromě jednoduchých datových typů si samozřejmě popíšeme i složené datové typy, především pole, řetězce, řezy a později i struktury a mapy.

Doba čtení: 33 minut

11. Kopie polí

12. Řezy (slices), jejich význam a rozdílné vlastnosti oproti polím

13. Základní operace s řezy

14. Pokročilejší operace s řezy

15. Vytvoření řezu z jiného řezu

16. Datové typy popsané příště

17. Speciální hodnota nil a její specifický význam v jazyku Go

18. Repositář s demonstračními příklady

19. Odkazy na Internetu

1. Datové typy v programovacím jazyku Go

V dnešním článku se seznámíme s typovým systémem programovacího jazyka Go. Připomeňme si, že Go používá statický typový systém, což konkrétně znamená, že typ proměnné, parametru funkce, návratové hodnoty funkce atd. musí být známý již v době překladu (compile time) a není ho tedy nutné vyhodnocovat a kontrolovat v čase běhu programu (runtime). Datové typy, které programovací jazyk Go vývojářům nabízí, je možné rozdělit podle několika kritérií. Základní dělení je na jednoduché či primitivní datové typy (typicky se hodnoty těchto typů dají uložit do jediného registru mikroprocesoru), složené datové typy a některé zvláštní typy (zde se konkrétně jedná o ukazatel, funkci, rozhraní a taktéž kanál). Jednoduché datové typy se někdy dále dělí na ordinální a neordinální. Celá hierarchie typového systému jazyka Go je zobrazena pod tímto odstavcem:

Poznámka: v Go je samozřejmě podporována deklarace vlastních datových typů, což je velmi důležité téma, kterému se budeme věnovat příště.
Pro doplnění se můžete podívat, jak vypadá základ typového systému Rustu .

2. Celočíselné datové typy

Programovací jazyk Go je navržen takovým způsobem, aby ho bylo možné efektivně provozovat na mainstreamových mikroprocesorových architekturách, což konkrétně znamená především architektury x86 (32bit), x86–64 (64bit), ARM32 (velká rodina několika 32bitových architektur), taktéž AArch64 (64bit) popř. další architetury (MIPS, PowerPC, …). Z tohoto důvodu v Go nalezneme takové celočíselné datové typy, které jsou na zmíněných architekturách nativně zpracovávány. To je poměrně značně odlišná strategie, než kterou se kdysi vydal programovací jazyk C, v němž se formát a šířka celočíselných datových typů odvozuje od použité architektury (například různé mikrořadiče či DSP používají mnohdy zcela odlišné formáty, ostatně podobně, jako tomu bylo na minipočítačích PDP atd.).

V následující tabulce jsou vypsány všechny celočíselné datové typy poskytované jazykem Go 1.x. Každý celočíselný datový typ je jednoznačně určen jediným klíčovým slovem; nenajdeme zde tedy modifikátory signed, unsigned, short, long atd.:

Označení Rozsah hodnot Stručný popis
int8 –128 až 127 osmibitové celé číslo se znaménkem
int16 –32768 až 32767 16bitové celé číslo se znaménkem
int32 –2147483648 až 2147483647 32bitové celé číslo se znaménkem
int64 –9223372036854775808 až 9223372036854775807 64bitové celé číslo se znaménkem
     
uint8 0 až 255 osmibitové celé číslo bez znaménka
uint16 0 až 65535 16bitové celé číslo bez znaménka
uint32 0 až 4294967295 32bitové celé číslo bez znaménka
uint64 0 až 18446744073709551615 64bitové celé číslo bez znaménka
     
int různý odpovídá buď typu int32 nebo int64
uint různý odpovídá buď typu uint32 nebo uint64
     
byte 0 až 255 alias pro typ uint8
rune –2147483648 až 2147483647 alias pro typ int32
uintptr různý používáno pro uložení adresy (ukazatele)
Povšimněte si, že v předchozí tabulce byl u většiny celočíselných datových typů přesně uveden rozsah hodnot. Je tomu tak z toho důvodu, že specifikace jazyka Go určuje přesnou bitovou šířku typů a taktéž to, že celočíselné hodnoty se znaménkem mají být reprezentovány ve dvojkovém doplňku. To je poměrně velký rozdíl oproti již zmíněnému programovacímu jazyku C, v jehož specifikaci se u základního typu int a jeho variant (short, long, long long) naschvál všechny tyto informace neuvádí, protože céčko je provozováno i na mnohdy obskurních architekturách (například se může použít jedničkový doplněk atd.).

Dále stojí za povšimnutí existence takzvaných aliasů, které se používají ve třech významech. První aliasy int a unint odpovídají buď 32bitové nebo 64bitové šířce celočíselného datového typu v závislosti na architektuře (zde se alespoň do jisté míry přibližujeme k céčku); ovšem zajímavější jsou aliasy byte (vždy osmibitová hodnota bez znaménka) a rune (reprezentace znaku v Unicode). Poslední alias uintptr reprezentuje datový typ určený pro uložení ukazatele (pointeru) a je tedy opět závislý na konkrétní architektuře.

Ukažme si nyní příklad definice proměnných s uvedením typu a současně i s jejich inicializací:

package main
 
import "fmt"
 
func main() {
        var a int8 = -10
        var b int16 = -1000
        var c int32 = -10000
        var d int32 = -1000000
 
        var r1 rune = 'a'
        var r2 rune = '\x40'
        var r3 rune = '\n'
        var r4 rune = '\u03BB'
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
 
        fmt.Println(r1)
        fmt.Println(r2)
        fmt.Println(r3)
        fmt.Println(r4)
}

Výsledek po překladu a spuštění:

-10
-1000
-10000
-1000000
97
64
10
955

Překladač jazyka Go striktně hlídá, zda se přiřazovaná hodnota skutečně vejde do proměnné daného typu:

package main
 
import "fmt"
 
func main() {
        var a int8 = -1000
        var b int16 = -100000
        var c int32 = -10000000000
        var d int32 = -10000000000000000
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
}

Při pokusu o překlad předchozího příkladu získáme pouze čtveřici chybových hlášení:

./02_integer_signed_types_checks.go:13:15: constant -1000 overflows int8
./02_integer_signed_types_checks.go:14:16: constant -100000 overflows int16
./02_integer_signed_types_checks.go:15:16: constant -10000000000 overflows int32
./02_integer_signed_types_checks.go:16:16: constant -10000000000000000 overflows int32

Ve třetím příkladu vytvoříme čtyři lokální proměnné nesoucí informace o hodnotách bez znaménka:

package main
 
import "fmt"
 
func main() {
        var a uint8 = 10
        var b uint16 = 1000
        var c uint32 = 10000
        var d uint32 = 1000000
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
}

Výsledek po překladu a spuštění:

10
1000
10000
1000000

Celočíselné konstanty je možné zapisovat v desítkové soustavě, v soustavě osmičkové (prefixem je zde nula) nebo v soustavě šestnáctkové (prefixem je 0×). Způsob použití různých číselných soustav je ukázán ve čtvrtém demonstračním příkladu:

package main
 
import "fmt"
 
func main() {
        var a uint8 = 10
        var b uint8 = 010
        var c uint8 = 0x10
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
 
        var d int8 = -10
        var e int8 = -010
        var f int8 = -0x10
 
        fmt.Println(d)
        fmt.Println(e)
        fmt.Println(f)
}

Výsledek po překladu a spuštění:

10
8
16
-10
-8
-16

3. Konverze mezi hodnotami různých typů

V mnoha programovacích jazycích nalezneme různá více či méně složitá pravidla, která se týkají konverzí mezi hodnotami odlišných datových typů. V jazyce Go se podobná pravidla používají taktéž a nutno říci, že jsou poměrně silná. Tato pravidla například neumožní ani implicitní převody mezi typy uint8 a uint16, což si můžeme ukázat na pátém demonstračním příkladu:

package main
 
import "fmt"
 
func main() {
        var a uint8 = 255
        var b uint16 = a
 
        fmt.Println(a)
        fmt.Println(b)
}

Teoreticky by měla být konverze mezi osmibitovým číslem bez znaménka na 16bitové číslo (taktéž bez znaménka) bezproblémová. Přesto překladač jazyka Go tuto implicitní konverzi nepovolí, protože se řídí filozofií, že explicitní přetypování je z dlouhodobého hlediska výhodnější než sada implicitních pravidel:

./05_improper_conversion.go:14:6: cannot use a (type uint8) as type uint16 in assignment

Podobně je tomu i při opačné konverzi, kde samozřejmě dochází ke ztrátě informace:

package main
 
import "fmt"
 
func main() {
        var a uint16 = 255
        var b uint8 = a
 
        fmt.Println(a)
        fmt.Println(b)
}

Výsledek pokusu o překlad:

./06_improper_conversion.go:14:6: cannot use a (type uint16) as type uint8 in assignment

Taktéž implicitní převody mezi celočíselnými typy se stejnou bitovou šířkou, ovšem s odlišným rozsahem (se znaménkem, bez znaménka), nejsou povoleny, což si vyzkoušíme v sedmém demonstračním příkladu:

package main
 
import "fmt"
 
func main() {
        var a int8 = 100
        var b uint8 = a
 
        fmt.Println(a)
        fmt.Println(b)
 
        var c uint8 = 100
        var d int8 = c
 
        fmt.Println(c)
        fmt.Println(d)
}

Výsledek pokusu o překlad tohoto příkladu:

./07_improper_conversion_int_uint.go:14:6: cannot use a (type int8) as type uint8 in assignment
./07_improper_conversion_int_uint.go:20:6: cannot use c (type uint8) as type int8 in assignment

Naprostou většinu konverzí je nutné ve zdrojových kódech zapisovat explicitně tak, jak je to ukázáno v dalším demonstračním příkladu. Povšimněte si, že se zde nepoužívá klasické přetypování, ale zápis konverze vypadá jako volání běžné funkce:

package main
 
import "fmt"
 
func main() {
        var a int8 = -10
        var signed_int int32 = -100000
        var unsigned_int uint32 = 100000
        var e float32 = 1e4
        var f float64 = 1.5e30
 
        var x int32 = int32(a)
        var y int32 = int32(e)
        var z float32 = float32(f)
 
        fmt.Println(x)
        fmt.Println(y)
        fmt.Println(z)
 
        var b2 uint8 = uint8(signed_int)
        var b3 uint8 = uint8(unsigned_int)
 
        fmt.Println(b2)
        fmt.Println(b3)
}

Tento příklad již půjde bez problémů přeložit i spustit:

-10
10000
1.5e+30
96
160

4. Tisk a formátování celočíselných hodnot

Pro tisk a popř. i základní naformátování celočíselných hodnot na standardním či na chybovém výstupu slouží funkce fmt.Printf. Tato funkce jako první parametr akceptuje takzvaný formátovací řetězec, jehož obsah je do jisté míry podobný, jako je tomu v céčkovské standardní funkci printf, popř. javovské metodě String.format. Pro celočíselné datové typy se používá především použití dekadické soustavy (%d), šestnáctkové soustavy (%x), určení šířky/počtu cifer (%5d), specifikace doplnění nul zleva (%05d), změna zarovnání (%-5d) a vynucení tisku znaménka (%+5d). Podívejme se na následující příklad s několika možnostmi formátování:

package main
 
import "fmt"
 
func main() {
        var a uint8 = 20
        var b uint16 = 2000
        var c uint32 = 20000
        var d uint32 = 2000000
 
        fmt.Println("%d")
        fmt.Printf("%d\n", a)
        fmt.Printf("%d\n", b)
        fmt.Printf("%d\n", c)
        fmt.Printf("%d\n", d)
 
        fmt.Println("\n%5d")
        fmt.Printf("%5d\n", a)
        fmt.Printf("%5d\n", b)
        fmt.Printf("%5d\n", c)
        fmt.Printf("%5d\n", d)
 
        fmt.Println("\n%05d")
        fmt.Printf("%05d\n", a)
        fmt.Printf("%05d\n", b)
        fmt.Printf("%05d\n", c)
        fmt.Printf("%05d\n", d)
 
        fmt.Println("\n%-5d")
        fmt.Printf("%-5d\n", a)
        fmt.Printf("%-5d\n", b)
        fmt.Printf("%-5d\n", c)
        fmt.Printf("%-5d\n", d)
 
        fmt.Println("\n%+5d")
        fmt.Printf("%+5d\n", a)
        fmt.Printf("%+5d\n", b)
        fmt.Printf("%+5d\n", c)
        fmt.Printf("%+5d\n", d)
 
        fmt.Println("\n%x")
        fmt.Printf("%x\n", a)
        fmt.Printf("%x\n", b)
        fmt.Printf("%x\n", c)
        fmt.Printf("%x\n", d)
 
        fmt.Println("\n%X")
        fmt.Printf("%X\n", a)
        fmt.Printf("%X\n", b)
        fmt.Printf("%X\n", c)
        fmt.Printf("%X\n", d)
 
        fmt.Println("\n%b")
        fmt.Printf("%b\n", a)
        fmt.Printf("%b\n", b)
        fmt.Printf("%b\n", c)
        fmt.Printf("%b\n", d)
}

Výsledky ukazují vliv, jaký má formátovací řetězec na výsledný tvar čísla na standardním výstupu:

%d
20
2000
20000
2000000
 
%5d
   20
 2000
20000
2000000
 
%05d
00020
02000
20000
2000000
 
%-5d
20
2000
20000
2000000
 
%+5d
  +20
+2000
+20000
+2000000
 
%x
14
7d0
4e20
1e8480
 
%X
14
7D0
4E20
1E8480
 
%b
10100
11111010000
100111000100000
111101000010010000000

U záporných čísel se vždy bude vypisovat znaménko, nehledě na použití %+.. ve formátovacím řetězci:

package main
 
import "fmt"
 
func main() {
        var a int8 = -10
        var b int16 = -1000
        var c int32 = -10000
        var d int32 = -1000000
 
        var r1 rune = 'a'
        var r2 rune = '\x40'
        var r3 rune = '\n'
        var r4 rune = '\u03BB'
 
        fmt.Println("%d")
        fmt.Printf("%d\n", a)
        fmt.Printf("%d\n", b)
        fmt.Printf("%d\n", c)
        fmt.Printf("%d\n", d)
 
        fmt.Println("\n%5d")
        fmt.Printf("%5d\n", a)
        fmt.Printf("%5d\n", b)
        fmt.Printf("%5d\n", c)
        fmt.Printf("%5d\n", d)
 
        fmt.Println("\n%05d")
        fmt.Printf("%05d\n", a)
        fmt.Printf("%05d\n", b)
        fmt.Printf("%05d\n", c)
        fmt.Printf("%05d\n", d)
 
        fmt.Println("\n%-5d")
        fmt.Printf("%-5d\n", a)
        fmt.Printf("%-5d\n", b)
        fmt.Printf("%-5d\n", c)
        fmt.Printf("%-5d\n", d)
 
        fmt.Println("\n%+5d")
        fmt.Printf("%+5d\n", a)
        fmt.Printf("%+5d\n", b)
        fmt.Printf("%+5d\n", c)
        fmt.Printf("%+5d\n", d)
 
        fmt.Println("\n%x")
        fmt.Printf("%x\n", a)
        fmt.Printf("%x\n", b)
        fmt.Printf("%x\n", c)
        fmt.Printf("%x\n", d)
 
        fmt.Println("\n%X")
        fmt.Printf("%X\n", a)
        fmt.Printf("%X\n", b)
        fmt.Printf("%X\n", c)
        fmt.Printf("%X\n", d)
 
        fmt.Println("\n%b")
        fmt.Printf("%b\n", a)
        fmt.Printf("%b\n", b)
        fmt.Printf("%b\n", c)
        fmt.Printf("%b\n", d)
 
        fmt.Println("%c")
        fmt.Printf("%c\n", r1)
        fmt.Printf("%c\n", r2)
        fmt.Printf("%c\n", r3)
        fmt.Printf("%c\n", r4)
}

Výsledky:

%d
-10
-1000
-10000
-1000000
 
%5d
  -10
-1000
-10000
-1000000
 
%05d
-0010
-1000
-10000
-1000000
 
%-5d
-10
-1000
-10000
-1000000
 
%+5d
  -10
-1000
-10000
-1000000
 
%x
-a
-3e8
-2710
-f4240
 
%X
-A
-3E8
-2710
-F4240
 
%b
-1010
-1111101000
-10011100010000
-11110100001001000000
%c
a
@


λ
Poznámka: povšimněte si především způsobu výpisu znaků při použití %c.

5. Podmnožiny reálných čísel a komplexní čísla

V programovacím jazyku Go samozřejmě nalezneme i datové typy určené pro uložení podmnožiny reálných čísel. Podobně, jako je tomu v prakticky všech moderních programovacích jazycích, jsou tyto datové typy odvozeny od formátů numerických hodnot specifikovaných v normě IEEE 754 (ať již v její základní variantě, či novější verzi). K dispozici jsou tedy dva základní typy: čísla s jednoduchou přesností (single) a čísla s přesností dvojitou (double).

V mnoha programovacích jazycích se jména těchto typů odvozují skutečně od zmíněné normy IEEE 754, ovšem v Go (a podobně i v Rustu, i když Rust používá ještě kratší jména) jsou zvolena odlišná označení, konkrétně float32 a float64. Kromě toho najdeme v jazyku Go i typy pojmenované complex64 a complex128 pro reprezentaci komplexních čísel. Interně se jedná o dvojici float32+float32 nebo float64+float64:

Označení Rozsah hodnot Stručný popis
float32 –3,4×1038 až 3,4×1038 číslo s jednoduchou přesností podle IEEE 754
float64 –1,7×10308 až 1,7×10308 číslo s dvojitou přesností podle IEEE 754
complex64 ± rozsah float32 + i ± rozsah float32 dvojice hodnot s jednoduchou přesností
complex128 ± rozsah float64 + i ± rozsah float64 dvojice hodnot s dvojitou přesností

Nejdříve se podívejme na různé způsoby deklarací proměnných typu float32 a float64:

package main
 
import "fmt"
 
func main() {
        var a float32 = -1.5
        var b float32 = 1.5
        var c float32 = 1e30
        var d float32 = 1e-30
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
 
        var e float64 = -1.5
        var f float64 = 1.5
        var g float64 = 1e300
        var h float64 = 1e-300
 
        fmt.Println(e)
        fmt.Println(f)
        fmt.Println(g)
        fmt.Println(h)
}

Překladač jazyka Go opět kontroluje, zda se nesnažíme používat příliš velké konstanty, které neodpovídají povolenému rozsahu:

package main
 
func main() {
        var c float32 = 1e300
        var d float32 = -1e300
 
        var g float64 = 1e3000
        var h float64 = -1e3000
}

Výsledky kontroly překladačem:

./12_fp_types_checks.go:11:18: constant 1e+300 overflows float32
./12_fp_types_checks.go:12:18: constant -1e+300 overflows float32
./12_fp_types_checks.go:14:18: constant 1e+3000 overflows float64
./12_fp_types_checks.go:15:18: constant -1e+3000 overflows float64

Vyzkoušejme se nyní, jakým způsobem se zapisují hodnoty typu complex64 nebo complex128. V jazyce Go se používá zápis, který pravděpodobně znáte z matematiky: za druhé číslo se připojuje označení imaginární jednotky. Ta je v Go zapisována znakem i (matematický způsob) a nikoli j (používáno v elektro oborech, taktéž v Pythonu). Nutno říci, že zápis s „i“ je z typografického hlediska hezčí :-). V dalším programu si povšimněte, že můžeme zapsat pouze imaginární část čísla, ovšem před „i“ musí být vždy alespoň jedna číslice (jinak by došlo k chybě popř. k přiřazení hodnoty proměnné i, pokud náhodou existuje):

package main
 
import "fmt"
 
func main() {
        var a complex64 = -1.5 + 0i
        var b complex64 = 1.5 + 1000i
        var c complex64 = 1e30 + 1e30i
        var d complex64 = 1i
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
 
        var e complex128 = -1.5 + 0i
        var f complex128 = 1.5 + 1000i
        var g complex128 = 1e300 + 1e300i
 
        fmt.Println(e)
        fmt.Println(f)
        fmt.Println(g)
}

Zajímavý je způsob zobrazení hodnot komplexních čísel – celá hodnota je umístěna do závorek:

(-1.5+0i)
(1.5+1000i)
(1e+30+1e+30i)
(0+1i)
(-1.5+0i)
(1.5+1000i)
(1e+300+1e+300i)
Poznámka: pozor na poněkud matoucí výstup (1e+300+1e+300i), protože prostřední znak + spojuje reálnou a imaginární složku, zatímco ostatní dva znaky + se vztahují k exponentu.

6. Pravdivostní hodnoty

V programovacím jazyku Go je samozřejmě možné pracovat i s pravdivostními hodnotami, které jsou reprezentovány pravdivostním typem (bool, boolean). Tento datový typ používá pouze dvě hodnoty, které jsou pojmenovány klasicky true a false (pro Pythonisty: s malými písmeny na začátku). Příklad základního použití tohoto datového typu je velmi jednoduchý:

package main
 
import "fmt"
 
func main() {
        var a bool = true
        var b bool = false
 
        fmt.Println(a)
        fmt.Println(b)
}

Na tomto místě je vhodné upozornit na fakt, že v programovacím jazyce Go není možné provést automatický a implicitní převod mezi celočíselným typem a pravdivostní hodnotou. Podobně nelze provést převod mezi řetězcem (polem atd.) a pravdivostní hodnotou, na rozdíl od některých jiných programovacích jazyků (příkladem může být Python s několika hodnotami, které se vyhodnotí jako false, kdežto všechny ostatní hodnoty mohou být v logických výrazech považovány za true). Vyzkoušejme si tuto vlastnost jazyka Go na dalším demonstračním příkladu:

package main
 
import "fmt"
 
func main() {
        var a bool = true
        var b bool = false
        var c bool = 0
        var d bool = ""
 
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
}

Při pokusu o překlad tohoto zdrojového kódu se zobrazí chybová hlášení o nemožnosti provedení konverze:

./15_boolean_type_checks.go:15:6: cannot use 0 (type int) as type bool in assignment
./15_boolean_type_checks.go:16:15: cannot use "" (type string) as type bool in assignment

7. Řetězce

Naprostá většina moderních programovacích jazyků podporuje práci s řetězci, přesněji řečeno řetězce jsou plnohodnotným datovým typem (výjimkou je vlastně jen klasické céčko, které rozpoznává jen řetězcové literály). Nejinak je tomu i v programovacím jazyce Go, v němž nalezneme datový typ string. Interně se s řetězcem pracuje stejně, jako s řezem (slice) pole bajtů (s pojmem řez se seznámíme v dalších kapitolách). A právě z tohoto způsobu práce s řetězci vychází i jejich základní vlastnosti, které mohou některé programátory překvapit a je dobré je znát:

  1. V první řadě jsou řetězce neměnitelné (immutable).
  2. Dále je u řetězců známá jejich délka, ovšem udávaná v bajtech, nikoli ve znacích.
  3. Při přístupu k jednotlivým prvkům řetězce pomocí operátoru indexování [] získáme jednotlivé bajty, nikoli znaky!
  4. A za čtvrté: v řetězci je skutečně možné mít uloženy libovolné kombinace bajtů. Teprve až ve chvíli, kdy budeme chtít pracovat s jednotlivými znaky, budou se jednotlivé sekvence bajtů transformovat do Unicode (k tomu je ovšem nutné použít buď jednu z forem smyčkyfor nebo knihovní funkce).

I když jsou řetězce interně reprezentovány jako sekvence bajtů, můžeme ve zdrojovém kódu používat řetězcové literály obsahující znaky z Unicode. Z předchozího článku již víme, že zdrojové kódy psané v Go jsou uloženy v UTF-8, takže by to nemělo představovat žádný problém. Povšimněte si, že samozřejmě můžeme použít prázdný řetězec a v řetězci mohou být umístěny řídicí znaky zapisované stejně, jako v C/C++, Javě atd. atd:

package main
 
import "fmt"
 
func main() {
        var s1 string = "www.root.cz"
        var s2 string = ""
        var s3 string = "Hello\nworld!\n"
        var s4 string = "шщэюя"
 
        fmt.Println(s1)
        fmt.Println(s2)
        fmt.Println(s3)
        fmt.Println(s4)
}

V některých situacích může být výhodné zapsat řetězec v přesně takové podobě, jak má být uložen v paměti; tj. budeme potřebovat zrušit význam řídicích znaků „\n“ atd. I to je možné, protože programovací jazyk Go podporuje takzvané „raw“ (surové) řetězce, které se nezapisují do uvozovek, ale do zpětných apostrofů (backtick). Díky tomuto způsobu zápisu řetězce se vyhneme nutnosti zdvojování zpětného lomítka a podobných triků:

package main
 
import "fmt"
 
func main() {
        var s1 string = "Hello\nworld!\n"
        var s2 string = `Hello\nworld!\n`
 
        fmt.Println(s1)
        fmt.Println(s2)
}

Nakonec si ještě ukážeme – prozatím bez hlubší analýzy – jak vlastně vypadá pole bajtů vytvořené z řetězcového literálu, který obsahuje různé znaky z Unicode:

package main
 
import "fmt"
 
func main() {
        var s string = "Hello\nworld!\nžluťoučký kůň"
 
        for i := 0; i < len(s); i++ {
                fmt.Printf("%02x ", s[i])
        }
 
}

Po spuštění tohoto příkladu by se na standardním výstupu měly objevit tyto hodnoty:

48 65 6c 6c 6f 0a 77 6f 72 6c 64 21 0a c5 be 6c 75 c5 a5 6f 75 c4 8d 6b c3 bd 20 6b c5 af c5 88

Příště si ukážeme užitečnější příklad, v němž se budeme řetězci zabývat podrobněji.

8. Deklarace lokálních proměnných

S deklaracemi proměnných jsme se již setkali v první části tohoto seriálu, takže si jen krátce zopakujme, že lokální proměnné je nutné deklarovat a popř. je můžeme i inicializovat. Při zápisu můžeme použít zkrácený zápis – vzhledem k tomu, že překladač pozná, jakého typu je hodnota přiřazovaná do proměnné, může být explicitní specifikace jejího typu poněkud nadbytečné. Pro zkrácený zápis deklarace proměnné s její inicializací je v programovacím jazyku Go vytvořen operátor :=, který se používá následujícím způsobem (opět ho známe z předchozího článku):

package main
 
import "fmt"
 
func main() {
        a := 10
        fmt.Println(a)
        b := "hello"
        fmt.Println(b)
        c := true
        fmt.Println(c)
}

9. Implicitní hodnoty proměnných

Dále je ve specifikaci jazyka Go přesně určeno, jaké hodnoty budou mít neinicializované proměnné. U číselných hodnot je to nula (reprezentovaná různým způsobem), u řetězců prázdný řetězec atd. Toto chování platí i pro lokální proměnné:

package main
 
import "fmt"
 
func main() {
        var i1 int8
        var i2 int32
        var u1 uint8
        var u2 uint32
 
        var f1 float32
        var f2 float64
        var c1 complex64
        var c2 complex128
 
        var s string
 
        fmt.Println(i1)
        fmt.Println(i2)
        fmt.Println(u1)
        fmt.Println(u2)
 
        fmt.Println(f1)
        fmt.Println(f2)
        fmt.Println(c1)
        fmt.Println(c2)
 
        fmt.Println(s)
}

Tento příklad půjde bez problémů přeložit a spustit s těmito výsledky (včetně prázdného řádku na konci):

0
0
0
0
0
0
(0+0i)
(0+0i)
 

10. Pole

V programovacím jazyce Go se, podobně jako v mnoha dalších programovacích jazycích, setkáme s poli (array). Pole jsou homogenní, což znamená, že prvky pole mají vždy stejný a již v čase překladu známý typ. Pole mají současně (minimálně v Go) neměnnou délku, kterou je nutné specifikovat při vytváření pole. Současně jsou pole plnohodnotnými datovými typy s vlastní sémantikou a dokonce i vlastními metadaty, na rozdíl od jazyka C, v němž je práce se skutečnými poli v mnoha případech omezena typovým systémem.

Podívejme se nejdříve na způsob definice pole bez jeho přímé inicializace. Prvky pole budou mít v takovém případě nulovou hodnotu:

var a1 [10]byte

Pole ovšem můžeme současně inicializovat:

a3 := [10]int32{1,10,2,9,3,8,4,7,5,6}

„Čtení“ datového typu je zde provedeno přímočaře zleva doprava: „pole deseti prvků typu int32 s hodnotami 1,10 …“

Vytvořit můžeme i vícerozměrná pole:

var matice [10][10]float32

Pro zjištění délky pole použijeme funkci len, tj. například:

x := len(a1)

A pro přístup k prvkům se používají klasické hranaté závorky, přičemž první prvek má nulový index:

for i:= 0; i < len(a1); i++ {
        a[i] = i*2;
}

Podívejme se nyní na úplný příklad, v němž se deklaruje a použije několik polí:

package main
 
import "fmt"
 
func main() {
        var a1 [10]byte
        var a2 [10]int32
        a3 := [10]int32{1,10,2,9,3,8,4,7,5,6}
 
        fmt.Printf("Delka pole 1: %d\n", len(a1))
        fmt.Printf("Delka pole 2: %d\n", len(a2))
        fmt.Printf("Delka pole 3: %d\n", len(a3))
 
 
        var a[10]int
 
        fmt.Printf("Pole pred upravou: %v\n", a)
 
        for i:= 0; i < len(a1); i++ {
                a[i] = i*2;
        }
 
        fmt.Printf("Pole po uprave:    %v\n", a)
 
        var matice [10][10]float32
        fmt.Printf("Matice:    %v\n", matice)
}
Pole ve skutečnosti nejsou příliš flexibilním datovým typem, mj. i z toho důvodu, že typová informace o poli v sobě zahrnuje i délku pole (počet prvků). Tím pádem je složité vytvářet například funkce akceptující jako svůj parametr pole (až na speciální případy typu transformační matice atd.). Ovšem to v praxi příliš nevadí, protože pole většinou slouží jako základ pro jiný typ – řez (slice).

11. Kopie polí

Zajímavé bude zjistit, co se stane v případě, že v programu vytvoříme novou proměnnou a přiřadíme jí pole:

a2 := a1

V tomto případě se sémantika odlišuje podle použitého jazyka:

  1. V Go se provede skutečná kopie pole, takže výsledkem budou dvě na sobě nezávislá pole.
  2. V Javě (například) se jen přiřadí reference, takže dvě proměnné budou ukazovat na stejné pole.

Chování Go si můžeme velmi snadno otestovat na následujícím příkladu, v němž nejprve vytvoříme kopii pole a posléze původní pole změníme:

package main
 
import "fmt"
 
func main() {
        var a1[10]int
 
        a2 := a1
 
        fmt.Printf("Pole 1: %v\n", a1)
        fmt.Printf("Pole 2: %v\n", a2)
 
        for i:= 0; i <len(a1); i++ {
                a1[i] = i*2;
        }
 
        fmt.Printf("Pole 1: %v\n", a1)
        fmt.Printf("Pole 2: %v\n", a2)
}

Výsledek odpovídá předchozímu popisu – pole jsou odlišná:

Pole 1: [0 0 0 0 0 0 0 0 0 0]
Pole 2: [0 0 0 0 0 0 0 0 0 0]
Pole 1: [0 2 4 6 8 10 12 14 16 18]
Pole 2: [0 0 0 0 0 0 0 0 0 0]

Přibližně syntakticky (ne sémanticky!) ekvivalentní program v Javě by mohl vypadat takto:

import java.util.Arrays;
 
public class Test {
    public static void main(String[] args) {
        int[] a1 = new int[10];
        int[] a2 = a1;
 
        for (int i=0; i<a1.length; i++) {
            a1[i] = i*2;
        }
 
        System.out.println(Arrays.toString(a1));
        System.out.println(Arrays.toString(a2));
    }
}

Po překladu a spuštění se ovšem vypíšou dva stejné řádky – to znamená, že v programové smyčce jsme měnili prvky pole sdíleného mezi proměnnými a1 a a2:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Poznámka: sice to nebylo primárním cílem porovnání, ale povšimněte si, že Javovská varianta je více „ukecanější“

12. Řezy (slices), jejich význam a rozdílné vlastnosti oproti polím

Jak jsme si již řekli v předchozích kapitolách, nemusí být klasické pole dostatečně flexibilní datovou strukturou, která by plně vyhovovala potřebám vytvářené aplikace popř. implementovaného algoritmu. Pro dosažení větší flexibility byl do programovacího jazyka Go přidán další datový typ nazývaný řez neboli slice. Interně se jedná o referenci na automaticky vytvořené pole nebo na pole, které je explicitně „nasalámováno“ operací, s níž se seznámíme v navazující kapitole. Každý řez je v operační paměti uložen ve formě trojice hodnot (jde o záznam – struct či record):

  1. Ukazatele (reference) na zvolený prvek pole s daty, ke kterým přes řez přistupujeme.
  2. Délky řezu, tj. počtu prvků.
  3. Kapacity řezu (do jaké míry může řez narůstat v důsledku přidávání dalších prvků).

Tato interní struktura řezů s sebou přináší několik zajímavých důsledků. Je totiž možné, aby existovalo větší množství řezů ukazujících na obecně různé prvky jediného pole. Pokud nyní změníme prvek v jednom řezu, znamená to, že se vlastně modifikuje obsah původního pole a i ostatní řezy nový prvek uvidí. Co je však užitečnější – s řezy jako s datovým typem se velmi snadno pracuje; řezy mohou být předávány do funkcí, vráceny z funkcí atd.

Poznámka: s řezy, i když měly poněkud jiné chování, jsme se seznámili například při popisu programovacího jazyka Rust. Viz řezy vektoru a řezy pole.

13. Základní operace s řezy

Datový typ řez (slice), s nímž jsme se setkali v předchozí kapitole, získal svůj název kvůli následující operaci provedené s polem:

a[index1:index2]

Výsledkem předchozího výrazu je nový (obecně zúžený) pohled na pole, který se nazývá řez.

Jednoduchým příkladem může získání několika řezů z pole šesti řetězců:

a := [6]string{"C", "C++", "Java", "Python", "Go", "Rust"}

Zkusme si vytvořit různé řezy a zobrazit si jejich výsledek:

slice1 := a[1:4]
slice2 := a[:3]
slice3 := a[2:]
slice4 := a[:]
 
fmt.Println("Array a =", a)
fmt.Println("slice1 =", slice1)
fmt.Println("slice2 =", slice2)
fmt.Println("slice3 =", slice3)
fmt.Println("slice4 =", slice4)

Výstup bude vypadat následovně:

Array a = [C C++ Java Python Go Rust]
slice1 = [C++ Java Python]
slice2 = [C C++ Java]
slice3 = [Java Python Go Rust]
slice4 = [C C++ Java Python Go Rust]

U řezů máme k dispozici již zmíněnou délku a kapacitu:

package main
 
import "fmt"
 
func main() {
        var a1 [100]byte
        var a2 [100]int32
 
        fmt.Printf("Delka pole 1: %d\n", len(a1))
        fmt.Printf("Delka pole 2: %d\n", len(a2))
 
        var slice1 []byte = a1[10:20]
        fmt.Printf("Delka rezu 1:    %d\n", len(slice1))
        fmt.Printf("Kapacita rezu 1: %d\n", cap(slice1))
 
        var slice2 = a1[20:30]
        fmt.Printf("Delka rezu 2:    %d\n", len(slice2))
        fmt.Printf("Kapacita rezu 2: %d\n", cap(slice2))
 
        slice3 := a1[30:40]
        fmt.Printf("Delka rezu 3:    %d\n", len(slice3))
        fmt.Printf("Kapacita rezu 3: %d\n", cap(slice3))
}

Důležité je, že kapacita je počítána takovým způsobem, že dosahuje až do konce pole, tj. je obecně větší, než délka řezu. To je užitečné pro mnoho operací, které budou popsány příště:

Delka pole 1: 100
Delka pole 2: 100
Delka rezu 1:    10
Kapacita rezu 1: 90
Delka rezu 2:    10
Kapacita rezu 2: 80
Delka rezu 3:    10
Kapacita rezu 3: 70

14. Pokročilejší operace s řezy

Vzhledem k tomu, že řez je pouhým „pohledem“ na pole, znamená to, že modifikace prvků pole bude viditelná i při přístupu k prvkům řezu a naopak. Toto chování je ukázáno na dalším příkladu, v němž se nejdříve prvky modifikují přes pole a posléze přes řez:

package main
 
import "fmt"
 
func main() {
        var a[10]int

        slice := a[:]

        fmt.Printf("Pole před modifikací: %v\n", a)
        fmt.Printf("Řez před modifikací:  %v\n", slice)
 
        for i:= 0; i < len(a); i++ {
                a[i] = i*2;
        }
 
        fmt.Printf("Pole po modifikací:   %v\n", a)
        fmt.Printf("Řez po modifikaci:    %v\n", slice)
 
        for i:= 0; i < len(slice); i++ {
                slice[i] = 42;
        }
 
        fmt.Printf("Pole po modifikací:   %v\n", a)
        fmt.Printf("Řez po modifikaci:    %v\n", slice)
}

Výsledek by nás už neměl překvapit:

Pole před modifikací: [0 0 0 0 0 0 0 0 0 0]
Řez před modifikací:  [0 0 0 0 0 0 0 0 0 0]
 
Pole po modifikací:   [0 2 4 6 8 10 12 14 16 18]
Řez po modifikaci:    [0 2 4 6 8 10 12 14 16 18]
 
Pole po modifikací:   [42 42 42 42 42 42 42 42 42 42]
Řez po modifikaci:    [42 42 42 42 42 42 42 42 42 42]

15. Vytvoření řezu z jiného řezu

V praxi se taktéž můžeme setkat s tím, že se vytvoří řez z jiného řezu. Modifikace prvků provedená ve druhém řezu se samozřejmě přímo „propisuje“ jak do původního řezu, tak i do pole, z něhož byl původní řez vytvořen. V dalším příkladu takto modifikujeme prvky přes řez slice2:

package main
 
import "fmt"
 
func main() {
        var a[10]int
 
        slice1 := a[4:9]
        slice2 := slice1[3:]
 
        fmt.Printf("Pole:            %v\n", a)
        fmt.Printf("Delka pole:      %d\n\n", len(a))
 
        fmt.Printf("Rez 1:           %v\n", slice1)
        fmt.Printf("Delka rezu 1:    %d\n", len(slice1))
        fmt.Printf("Kapacita rezu 1: %d\n\n", cap(slice1))
 
        fmt.Printf("Rez 2:           %v\n", slice2)
        fmt.Printf("Delka rezu 2:    %d\n", len(slice2))
        fmt.Printf("Kapacita rezu 2: %d\n\n", cap(slice2))
 
        slice2[0] = 99
        slice2[1] = 99
 
        fmt.Printf("Pole:            %v\n", a)
        fmt.Printf("Rez 1:           %v\n", slice1)
        fmt.Printf("Rez 2:           %v\n", slice2)
}

Výsledek zobrazený po spuštění příkladu by měl vypadat následovně:

Pole:            [0 0 0 0 0 0 0 0 0 0]
Delka pole:      10
 
Rez 1:           [0 0 0 0 0]
Delka rezu 1:    5
Kapacita rezu 1: 6
 
Rez 2:           [0 0]
Delka rezu 2:    2
Kapacita rezu 2: 3
 
Pole:            [0 0 0 0 0 0 0 99 99 0]
Rez 1:           [0 0 0 99 99]
Rez 2:           [99 99]

16. Datové typy popsané příště

V navazujícím článku téma datových typů v jazyce Go dokončíme. Zabývat se budeme především ukazateli, záznamy (record, struct), mapami, ale i rozhraními (interface) a do jisté míry i kanály (channel).

17. Speciální hodnota nil a její specifický význam v jazyku Go

V jazyce Go se na několika místech můžeme setkat se speciální hodnotou nil. Tato hodnota se používá pro reprezentaci neinicializované hodnoty. Proto má nil význam především u těch datových typů, kde je nutné neinicializovanou hodnotu odlišit od ostatních hodnot. Příkladem mohou být ukazatele, rozhraní (popíšeme si je příště), mapy popř. řezy (prázdný řez je odlišný od řezu neinicializovaného). nil se naopak nepoužívá u číselných typů, řetězců ani u pravdivostních hodnot, protože v tomto případě je vždy proměnná inicializována – na nulu u číselných typů, na prázdný řetězec u řetězců a na hodnotu false u pravdivostních hodnot:

Typ Výchozí hodnota
všechny numerické typy 0, 0.0, 0.0+0.0i atd.
řetězce ""
pole prvky rekurzivně inicializované dle této tabulky
ukazatel nil
řez nil
kanál nil
mapa nil
Poznámka: práce s nil ovšem není tak přímočará, jak by se mohlo na první pohled zdát. Podrobnosti si uvedeme příště při popisu rozhraní.

18. 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:

kopie polí
# Demonstrační příklad Popis Cesta
1 01_integer_signed_types.go celočíselné typy se znaménkem https://github.com/tisnik/go-root/blob/master/article02/01_in­teger_signed_types.go
2 02_integer_signed_types_checks.go kontrola rozsahu hodnot https://github.com/tisnik/go-root/blob/master/article02/02_in­teger_signed_types_checks­.go
3 03_integer_unsigned_types.go celočíselné typy bez znaménka https://github.com/tisnik/go-root/blob/master/article02/03_in­teger_unsigned_types.go
4 04_integer_constants.go číselné soustavy https://github.com/tisnik/go-root/blob/master/article02/04_in­teger_constants.go
5 05_improper_conversion.go zakázaná implicitní konverze https://github.com/tisnik/go-root/blob/master/article02/05_im­proper_conversion.go
6 06_improper_conversion.go zakázaná implicitní konverze https://github.com/tisnik/go-root/blob/master/article02/06_im­proper_conversion.go
7 07_improper_conversion_int_uint.go zakázaná implicitní konverze https://github.com/tisnik/go-root/blob/master/article02/07_im­proper_conversion_int_uin­t.go
8 08_explicit_conversions.go explicitní konverze https://github.com/tisnik/go-root/blob/master/article02/08_ex­plicit_conversions.go
9 09_formatting_output.go formátování výstupu https://github.com/tisnik/go-root/blob/master/article02/09_for­matting_output.go
10 10_formatting_output.go formátování výstupu https://github.com/tisnik/go-root/blob/master/article02/10_for­matting_output.go
11 11_fp_types.go typy s plovoucí řádovou čárkou https://github.com/tisnik/go-root/blob/master/article02/11_fp_ty­pes.go
12 12_fp_types_checks.go kontrola rozsahu hodnot https://github.com/tisnik/go-root/blob/master/article02/12_fp_ty­pes_checks.go
13 13_complex_types.go komplexní čísla https://github.com/tisnik/go-root/blob/master/article02/13_com­plex_types.go
14 14_boolean_type.go pravdivostní hodnoty https://github.com/tisnik/go-root/blob/master/article02/14_bo­olean_type.go
15 15_boolean_type_checks.go nekorektní konverze https://github.com/tisnik/go-root/blob/master/article02/15_bo­olean_type_checks.go
16 16_string_type.go datový typ řetězec https://github.com/tisnik/go-root/blob/master/article02/16_strin­g_type.go
17 17_raw_strings.go surové řetězce https://github.com/tisnik/go-root/blob/master/article02/17_raw_strin­gs.go
18 18_string_content.go výpis bajtů reprezentujících řetězec https://github.com/tisnik/go-root/blob/master/article02/18_strin­g_content.go
19 19_local_variables.go lokální proměnné https://github.com/tisnik/go-root/blob/master/article02/19_lo­cal_variables.go
20 20_arrays.go pole https://github.com/tisnik/go-root/blob/master/article02/20_a­rrays.go
21 21_array_copy.go https://github.com/tisnik/go-root/blob/master/article02/21_a­rray_copy.go
22 22_slices.go řezy polí https://github.com/tisnik/go-root/blob/master/article02/22_sli­ces.go
23 23_slice_copy.go propisování hodnot mezi polem a řezem https://github.com/tisnik/go-root/blob/master/article02/23_sli­ce_copy.go
24 24_slice_from_slice.go vytvoření řezu z jiného řezu https://github.com/tisnik/go-root/blob/master/article02/24_sli­ce_from_slice.go

19. 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
Našli jste v článku chybu?