Hlavní navigace

Datové typy v programovacím jazyku Go (2.část)

Pavel Tišnovský

I ve třetí části seriálu o programovacím jazyku Go se budeme věnovat popisu typového systému. Popíšeme si především práci se záznamy (strukturami), mapami, ukazateli a taktéž funkcemi, které jsou v Go plnohodnotným datovým typem.

Doba čtení: 33 minut

11. Ukazatele

12. Ukazatel na strukturu (záznam)

13. Ukazatel na položku záznamu

14. Ukazatel na vybraný prvek pole

15. Funkce jakožto plnohodnotný datový typ

16. Funkce s parametry a její typ

17. Definice vlastního datového typu funkce s návratovou hodnotou

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

19. Odkazy na Internetu

1. Datové typy v programovacím jazyku Go (2.část)

Typový systém programovacího jazyka Go je poměrně rozsáhlý, protože kromě jednoduchých typů, polí a řetězců (které nalezneme v mnoha dalších jazycích) je možné pracovat i se záznamy (strukturami), mapami (asociativními poli), ukazateli, rozhraními a kanály. Kromě toho tento programovací jazyk považuje i funkce za plnohodnotný datový typ, což je téma, kterému se dnes budeme věnovat v poslední části článku.

Na tomto místě je vhodné (znovu) poznamenat, že Go není nadmnožinou programovacího jazyka C, protože například při práci s ukazateli je možné využít jen dosti omezené množství operací a navíc je většina operací s ukazateli typově bezpečná.

Při pohledu na hierarchii datových typů programovacího jazyka Go můžeme vidět, že v předchozím a dnešním článku jsme si popsali většinu základních datových typů. Příště toto téma dokončíme popisem rozhraní a především pak kanálů (channel), které jsou jedním z důvodů, proč programátoři na Go vlastně přechází:

2. Deklarace vlastních uživatelských typů

V úvodní části dnešního článku si ukážeme, jakým způsobem je možné v programovacím jazyku Go deklarovat vlastní uživatelské typy. Z předchozích dvou článků již víme, že Go je staticky a silně typovaným jazykem, takže možnost vytvářet vlastní datové typy je velmi užitečná a jak uvidíme v dalších příkladech, umožňují uživatelsky definované datové typy (pokud se využívají skutečně důsledně) tvorbu bezpečnějších aplikací.

Nový uživatelský datový typ se vytváří s využitím klíčového slova type, za nímž se uvede jméno (identifikátor) nového typu a posléze i jeho vlastní definice. Pokud například budeme chtít vytvořit nový datový typ pojmenovaný Name, který bude odvozený od klasického řetězce (string), můžeme to provést tímto způsobem:

type Name string
Poznámka: jméno datového typu může začínat malým nebo velkým písmenem, které slouží k rozlišení, zda se má jednat o veřejný či naopak soukromý typ. Tímto tématem se budeme zabývat příště.

Proměnnou nazvanou n, která má být typu Name můžeme vytvořit nám jíž známým způsobem s použitím klíčového slova var (čteme „proměnná n je typu Name“). Ihned poté je možné této proměnné přiřadit hodnotu (která musí být řetězcem):

var n Name
n = "Jan"

Podívejme se nyní na nepatrně složitější příklad s definicí tří uživatelských typů pojmenovaných Id, Name a Surname:

package main
 
import "fmt"
 
type Id uint32
type Name string
type Surname string
 
 
func main() {
        var i Id
        i = 0
        fmt.Println(i)
 
        var n Name
        var s Surname
 
        n = "Jan"
        s = "Novák"
 
        fmt.Println(n)
        fmt.Println(s)
}

Po spuštění tohoto příkladu by se mělo vypsat:

0
Jan
Novák

V dalším demonstračním příkladu je ukázáno, jak se vytváří funkce, jejíž parametry mají být určitého konkrétního typu. S tvorbou funkcí jsme se již (i když prozatím jen povrchně) seznámili v úvodním článku o programovacím jazyce Go:

package main
 
import "fmt"
 
type Id uint32
type Name string
type Surname string
 
 
func register_user(id Id, name Name, surname Surname) {
        fmt.Printf("Registering: %d %s %s", id, name, surname)
}
 
func main() {
        var i Id = 1
        var n Name = "Jan"
        var s Surname = "Novák"
 
        register_user(i, n, s)
}

Spuštění příkladu:

Registering: 1 Jan Novák
Poznámka: typ parametrů se uvádí stejně jako typ proměnných až za vlastní identifikátor (tedy naopak, než v céčku o od něj odvozených jazyků). Způsob zápisu použitý v Go je ovšem pro nováčky jednodušší: čte/dekóduje se stejně, nezávisle na tom, o jak složitou definici typu se jedná.

Zkusme si nyní otestovat nepatrně složitější příklad, v němž vytvoříme nový datový typ pojmenovaný Mesic a následně vytvoříme proměnnou typu „pole dvanácti měsíců“:

package main
 
import "fmt"
 
type Mesic string
 
func main() {
        var mesice [12]Mesic
 
        fmt.Println(mesice)
 
        mesice[0] = "Leden"
        mesice[1] = "Únor"
        mesice[2] = "Březen"
        mesice[3] = "Duben"
        mesice[4] = "Květen"
        mesice[5] = "Červen"
        mesice[6] = "Červenec"
        mesice[7] = "Srpen"
        mesice[8] = "Září"
        mesice[9] = "Říjen"
        mesice[10] = "Listopad"
        mesice[11] = "Prosinec"
 
        fmt.Println(mesice)
}

Povšimněte si, že funkce fmp.Println dokáže bez problémů vytisknout i hodnotu celého pole:

[           ]
[Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad Prosinec]
Poznámka: na prvním řádku je zobrazeno jedenáct mezer, které tvoří oddělovače mezi dvanácti prázdnými řetězci.

Pole samozřejmě můžeme deklarovat a ihned (v jediném příkazu) i inicializovat, což je ukázáno ve třetím demonstračním příkladu:

package main
 
import "fmt"
 
type Mesic string
 
func main() {
        mesice := [12]Mesic {
                "Leden",
                "Únor",
                "Březen",
                "Duben",
                "Květen",
                "Červen",
                "Červenec",
                "Srpen",
                "Září",
                "Říjen",
                "Listopad",
                "Prosinec"}
 
        fmt.Println(mesice)
}

Výsledek zobrazený po spuštění tohoto příkladu:

[Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad Prosinec]

3. Silná typová kontrola

Důležitost a současně i velká praktická užitečnost uživatelských datových typů spočívá v tom, že programovací jazyk Go provádí při překladu velmi důkladnou typovou kontrolu. Nedovolí například provést přiřazení hodnoty z proměnné (či výrazu) odlišného typu, i když bráno do důsledků se může interně jednat o totožně reprezentovaná data nebo o data velmi jednoduše převoditelná (viz již zmíněnou nemožnost implicitní konverze mezi int8 a int16 apod.).

Podívejme se ostatně na pátý demonstrační příklad, který nebude možné přeložit, a to z toho důvodu, že se do proměnných typu Name a Surname snažíme přiřadit hodnotu z proměnné typu string. Z pohledu překladače programovacího jazyka Go se totiž jedná o rozdílné typy, i když interní způsob uložení Name a Surname je pochopitelně totožný s řetězcem. Takto silné typové kontroly sice mohou vypadat pedanticky, ovšem filozofií Go je používat vždy explicitní zápis (včetně případného přetypování):

package main
 
import "fmt"
 
type Id uint32
type Name string
type Surname string
 
func main() {
        var i Id
        i = 0
        fmt.Println(i)
 
        var str = "common string"
 
        var n Name = str
        var s Surname = str
 
        n = "Jan"
        s = "Novák"
 
        fmt.Println(n)
        fmt.Println(s)
}

Při pokusu o překlad se vypíšou dvě chybové zprávy:

./05_user_type_checks.go:23:6: cannot use str (type string) as type Name in assignment
./05_user_type_checks.go:24:6: cannot use str (type string) as type Surname in assignment

Skutečná užitečnost typového systému programovacího jazyka Go se ovšem ukáže až v šestém demonstračním příkladu. V něm máme opět deklarovanou funkci register_user, která akceptuje tři parametry s těmito typy: Id, Name a Surname. Při volání této funkce však omylem prohodíme jméno a příjmení. Pokud by funkce byla deklarovaná tak, že oba dva parametry by byly typu string, překladač by na omyl nemohl přijít. Taktéž pokud by typová kontrola byla prováděna méně pedantským způsobem, například zjištěním, že NameSurname jsou odvozeny od řetězce, takže se mezi nimi může provést konverze, překladač by na omyl nemusel upozornit. Ovšem Go používá výše zmíněnou silnou kontrolu, takže chyba bude korektně nahlášena:

package main
 
import "fmt"
 
type Id uint32
type Name string
type Surname string
 
func register_user(id Id, name Name, surname Surname) {
        fmt.Printf("Registering: %d %s %s", id, name, surname)
}
 
func main() {
        var i Id = 1
        var n Name = "Jan"
        var s Surname = "Novák"
 
        register_user(i, s, n)
}

Výsledek pokusu o překlad:

./06_type_check_func.go:25:15: cannot use s (type Surname) as type Name in argument to register_user
./06_type_check_func.go:25:15: cannot use n (type Name) as type Surname in argument to register_user

V následujícím (již sedmém) příkladu se uživatelské datové typy nepoužívají; příklad si uvádíme jen proto, abychom si připomněli, že pole (přesněji řečeno prvky pole) se při přiřazování kopírují:

package main
 
import "fmt"
 
func main() {
        mesice := [12]string{
                "Leden",
                "Únor",
                "Březen",
                "Duben",
                "Květen",
                "Červen",
                "Červenec",
                "Srpen",
                "Září",
                "Říjen",
                "Listopad",
                "Prosinec"}
 
        var mesice2 [12]string = mesice
 
        fmt.Println(mesice)
        fmt.Println(mesice2)
}

Výsledek po spuštění:

[Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad Prosinec]
[Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad Prosinec]

Hned navazující demonstrační příklad je sice velmi podobný příkladu předchozímu, ovšem zde se snažíme o přiřazení polí odlišného datového typu: první je totiž pole typu „dvanáct měsíců“, druhé pole je typu „dvanáct řetězců“, což jsou z pohledu překladače opět zcela odlišné a nekompatibilní datové typy:

package main
 
import "fmt"
 
type Mesic string
 
func main() {
        mesice := [12]Mesic{
                "Leden",
                "Únor",
                "Březen",
                "Duben",
                "Květen",
                "Červen",
                "Červenec",
                "Srpen",
                "Září",
                "Říjen",
                "Listopad",
                "Prosinec"}
 
        var mesice2 [12]string = mesice
 
        fmt.Println(mesice)
        fmt.Println(mesice2)
}

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

./08_typed_array_check.go:29:6: cannot use mesice (type [12]Mesic) as type [12]string in assignment

4. Záznamy (struktury)

V programovacím jazyku Go je možné deklarovat i složené (a obecně nehomogenní) datové struktury záznamy (record), které jsou ovšem v jazycích odvozených od céčka známé spíše pod jménem struktury (struct). Záznam se skládá z většího množství pojmenovaných položek, přičemž každá položka má přesně definovaný typ a současně i pozici v záznamu (pozice je důležitá ve chvíli, kdy se hodnota typu záznam vytváří, i když Go ve skutečnosti podporuje i explicitní zápis jmen a hodnot položek). Nový datový typ záznam se vytváří opět s využitím klíčového slova type; celá definice může vypadat následovně:

type User struct {
        id      uint32
        name    string
        surname string
}
Poznámka: povšimněte si, že se mezi definicemi jmen a typů nemusí zapisovat žádné oddělovače. V céčkovských jazycích je na tomto místě nutné použít čárky, ovšem programovací jazyk Go tyto (pro něj nadbytečné) syntaktické prvky nevyžaduje.

Pro přístup k položkám záznamu se používá standardní tečková notace:

var user1 User
 
user1.id = 1
user1.name = "Pepek"
user1.surname = "Vyskoč"

Záznamy (resp. přesněji řečeno jejich obsah) je možné vytisknout pomocí nám již známé funkce fmt.Println:

fmt.Println(user1)

Podívejme se nyní na úplný demonstrační příklad, v němž napřed definujeme nový datový typ záznam pojmenovaný User a následně vytvoříme lokální proměnnou tohoto typu, jejíž obsah vytiskneme:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        var user1 User
 
        fmt.Println(user1)
 
        user1.id = 1
        user1.name = "Pepek"
        user1.surname = "Vyskoč"
 
        fmt.Println(user1)
}

Po spuštění tohoto příkladu by se nejdříve měl vypsat čistě inicializovaný záznam (viz poznámka v navazující kapitole) a posléze záznam s položkami nastavenými uživatelem:

{0  }
{1 Pepek Vyskoč}

5. Inicializace záznamů

V navazujícím příkladu je ukázáno, jak lze jediným příkazem deklarovat proměnnou typu záznam a současně i naplnit položky záznamu:

user1 := User{
        1,
        "Pepek",
        "Vyskoč"}

Zde se již používá odlišný způsob zápisu, v němž je nutné oddělit jednotlivé hodnoty čárkou. Uzavírací složená závorka se zapisuje ihned za poslední položku (překladač si formát zápisu automaticky pohlídá).

Poznámka: hodnoty položek záznamu jsou od ostatních příkazů ve stejném bloku odsazeny o jeden tabelátor.

Upravený příklad vypadá takto:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        user1 := User{
                1,
                "Pepek",
                "Vyskoč"}
 
        fmt.Println(user1)
 
        user1.id = 2
        user1.name = "Josef"
        user1.surname = "Vyskočil"
 
        fmt.Println(user1)
}

Výsledek po spuštění:

{1 Pepek Vyskoč}
{2 Josef Vyskočil}

Programovací jazyk Go ve skutečnosti podporuje ještě jeden způsob deklarace a inicializace záznamu, v němž jsou jednotlivé položky uvedeny explicitně. Tento způsob inicializace je jednoznačnější a navíc i bezpečnější ve chvíli, kdy se změní datový typ User (například se do něj přidá další položka):

user1 := User{
        id:      1,
        name:    "Pepek",
        surname: "Vyskoč"}

Opět si ukažme celý <a>demonstrační příklad:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        user1 := User{
                id:      1,
                name:    "Pepek",
                surname: "Vyskoč"}
 
        fmt.Println(user1)
 
        user1.id = 2
        user1.name = "Josef"
        user1.surname = "Vyskočil"
 
        fmt.Println(user1)
}

Výsledek by měl být totožný s příkladem předchozím:

{1 Pepek Vyskoč}
{2 Josef Vyskočil}

Zajímavé je, že záznamy stejného typu (v našem případě záznamy typu User) je možné porovnávat operátorem ==. Interně se totiž porovnání provede s jednotlivými položkami, nikoli například tak, že by se pouze zjistily a porovnaly adresy obou porovnávaných záznamů. Podívejme se na jednoduchý příklad:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        user1 := User{
                id:      1,
                name:    "Pepek",
                surname: "Vyskoč"}
 
        var user2 User
        user2.id = 1
        user2.name = "Pepek"
        user2.surname = "Vyskoč"
 
        user3 := User{
                id:      1,
                name:    "Josef",
                surname: "Vyskočil"}
 
        fmt.Println(user1 == user2)
        fmt.Println(user1 == user3)
}

Po spuštění tohoto příkladu získáme výsledek porovnání user1 == user2 a user1 == user3:

true
false

Vidíme, že první dva záznamy jsou při porovnání shodné, i když se ve skutečnosti jedná o rozdílné bloky v operační paměti.

6. Pole záznamů

Podívejme se ještě na způsob vytvoření pole záznamů, což je v praxi poměrně často používaná datová struktura tvořící základ pro ještě častěji používaný řez záznamů. Pole, které může obsahovat tři záznamy typu User se vytvoří snadno podle nám již známého schématu:

var users [3]User
Poznámka: deklaraci opět můžeme číst „proměnná users je typu pole tří prvků typu User“.

Současně se automaticky provede inicializace pole. Již minule jsme se totiž zmínili o tom, že každá datová struktura je po své alokaci v paměti i inicializována (typicky na nulovou hodnotu v případě číselných typů, řetězce jsou inicializovány prázdnými řetězci a u většiny strukturovaných datových typů se používá speciální hodnota nil). V případě záznamu se toto pravidlo vztahuje na všechny jeho položky a v případě pole na všechny prvky pole (popř. i rekurzivně na položky vnořené struktury atd.):

var users [3]User
fmt.Println(users)

Druhý řádek vypíše:

[{0  } {0  } {0  }]
Poznámka: skutečně můžeme vidět, že každý prvek pole je správně (rekurzivně) inicializován – číselná položka na nulu, řetězec je prázdný.

Úplný demonstrační příklad s polem tří záznamů by mohl vypadat následovně:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        var users [3]User
        fmt.Println(users)
 
        user1 := User{
                id:      1,
                name:    "Pepek",
                surname: "Vyskoč"}
 
        var user2 User
        user2.id = 1
        user2.name = "Pepek"
        user2.surname = "Vyskoč"
 
        user3 := User{
                id:      1,
                name:    "Josef",
                surname: "Vyskočil"}
 
        users[0] = user1
        users[1] = user2
        users[2] = user3
        fmt.Println(users)
}

Dvojice zpráv zobrazená po spuštění:

[{0  } {0  } {0  }]
[{1 Pepek Vyskoč} {1 Pepek Vyskoč} {1 Josef Vyskočil}]

V praxi se taktéž někdy setkáme s tím, že se pole záznamů deklaruje a současně i inicializuje v jediném příkazu. Zápis je již nepatrně složitější a je v něm nutné dodržet formátovací pravidla programovacího jazyka Go, zejména použití čárek za jednotlivými prvky inicializovaného pole. Povšimněte si, že každý prvek pole je zapsán formou konstruktoru záznamu:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        var users = [3]User{
                User{
                        id:      1,
                        name:    "Pepek",
                        surname: "Vyskoč"},
                User{
                        id:      2,
                        name:    "Pepek",
                        surname: "Vyskoč"},
                User{
                        id:      3,
                        name:    "Josef",
                        surname: "Vyskočil"},
        }
        fmt.Println(users)
 
        var users2 = [...]User{
                User{
                        id:      1,
                        name:    "Pepek",
                        surname: "Vyskoč"},
                User{
                        id:      2,
                        name:    "Pepek",
                        surname: "Vyskoč"},
                User{
                        id:      3,
                        name:    "Josef",
                        surname: "Vyskočil"},
        }
        fmt.Println(users2)
}

Po překladu a spuštění získáme tyto dva totožné řádky:

[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}]
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}]

7. Mapy

V programovacím jazyku Go patří (možná poněkud překvapivě) mezi standardní datové typy i mapy (maps), které se v některých jiných jazycích nazývají asociativní pole či pouze hashe. Tyto strukturované datové typy slouží pro ukládání dvojic klíč-hodnota, přičemž typ klíčů i typ hodnot musí být známý již v době překladu (uplatňují se zde všechna pravidla pro silný typový systém jazyka Go).

Poznámka: tak jako v jiných jazycích platí, že klíče použité v jedné mapě musí být unikátní; hodnoty však mohou být naprosto libovolné.

Ukažme si nyní způsob deklarace proměnné pojmenované m1, která je typu mapa celé číslo:řetězec:

var m1 map[int]string

Samozřejmě můžeme vytvořit i jinou mapu, konkrétně mapu s klíči typu string a hodnotami typu User

var m2 map[string]User

V dalším příkladu vytvoříme proměnnou typu mapa a posléze se do ní pokusíme zapsat novou dvojici s klíčem rovným nule a hodnotou „nula“:

package main
 
import "fmt"
 
func main() {
        var m1 map[int]string
        fmt.Println(m1)
 
        m1[0] = "nula"
}

Při pokusu o spuštění tohoto příkladu ovšem dojde k běhové chybě, která vypadá takto:

map[]
panic: assignment to entry in nil map
 
goroutine 1 [running]:
main.main()
        /home/tester/go-root/article_03/15_uninitialized_map.go:16 +0x76
exit status 2

Co to znamená? Předchozí proměnná byla sice deklarována korektně, ovšem uplatnila se zde již dříve popsaná pravidla pro inicializaci hodnoty proměnné. Zde se konkrétně vytvořila mapa a byla jí přiřazena speciální hodnota nil. Pokud budeme skutečně chtít mapu použít (naplnit ji dvojicemi klíč-hodnota), musíme při inicializaci zavolat vestavěnou funkci make.

Korektní inicializace mapy je ukázána na dalším příkladu:

package main
 
import "fmt"
 
func main() {
        var m1 map[int]string = make(map[int]string)
        fmt.Println(m1)
 
        m1[0] = "nula"
        m1[1] = "jedna"
        m1[2] = "dva"
        m1[3] = "tri"
        m1[4] = "ctyri"
        m1[5] = "pet"
        m1[6] = "sest"
 
        fmt.Println(m1)
}

Po spuštění tohoto příkladu se nejdříve vypíše prázdná mapa a posléze mapa naplněná šesti dvojicemi:

map[]
map[6:sest 0:nula 1:jedna 2:dva 3:tri 4:ctyri 5:pet]
Poznámka: povšimněte si, že dvojice jsou vypsány v jiném pořadí, než jak byly vkládány do mapy. To je výchozí a očekávané chování této datové struktury.

Předchozí příklad je možné přepsat s využitím operátoru := takto:

package main
 
import "fmt"
 
func main() {
        m1 := make(map[int]string)
        fmt.Println(m1)
 
        m1[0] = "nula"
        m1[1] = "jedna"
        m1[2] = "dva"
        m1[3] = "tri"
        m1[4] = "ctyri"
        m1[5] = "pet"
        m1[6] = "sest"
 
        fmt.Println(m1)
}

8. Mapy a struktury

Do map se samozřejmě mohou ukládat i struktury, s jejichž deklarací a použitím jsme se dnes již seznámili. V dalším příkladu je deklarována mapa s dvojicemi string-User:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        m1 := make(map[string]User)
        fmt.Println(m1)
 
        m1["prvni"] = User{
                id:      1,
                name:    "Pepek",
                surname: "Vyskoč"}
 
        m1["druhy"] = User{
                id:      2,
                name:    "Josef",
                surname: "Vyskočil"}
 
        fmt.Println(m1)
}

Výsledek příkladu je předvídatelný – nejdříve se vypíše prázdná mapa a posléze mapa se dvěma dvojicemi klíč-hodnota:

map[]
map[prvni:{1 Pepek Vyskoč} druhy:{2 Josef Vyskočil}]

Podívejme se nyní na to, jak se příklad změní ve chvíli, kdy jsou klíče mapy taktéž uživatelský typ. Vkládání jednotlivých prvků do mapy je nyní nepatrně složitější:

package main
 
import "fmt"
 
type Key struct {
        id   uint32
        role string
}
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        m1 := make(map[Key]User)
        fmt.Println(m1)
 
        m1[Key{1, "admin"}] = User{
                id:      1,
                name:    "Pepek",
                surname: "Vyskoč"}
 
        m1[Key{2, "user"}] = User{
                id:      2,
                name:    "Josef",
                surname: "Vyskočil"}
 
        fmt.Println(m1)
}

Po spuštění tohoto příkladu by se měly vypsat tyto dva řádky:

map[]
map[{1 admin}:{1 Pepek Vyskoč} {2 user}:{2 Josef Vyskočil}]
Poznámka: ve skutečnosti mohou být prvky při výpisu prohozené.

9. Čtení hodnot z map

Při čtení hodnot z map, tj. při výběru prvků s využitím klíče, je někdy nutné rozlišit mezi hodnotou nil a situací, kdy se daná dvojice klíč-hodnota v mapě ve skutečnosti vůbec nenachází. Další programovací jazyky se k tomuto problému staví různě a nabízí funkce typu exist atd. V programovacím jazyku Go je situace řešena relativně elegantně – ve skutečnosti totiž při čtení z mapy získáme dvě hodnoty: vlastní prvek, který v mapě byl uložen (popř. nil) a taktéž příznak, zda byl prvek pod daným klíčem nalezen. Můžeme tedy použít tento zápis:

value, exist := mapa[klíč]
Poznámka: opět používáme operátor :=, tj. nemusíme explicitně specifikovat typy obou nových lokálních proměnných.

V proměnné exist nyní bude uložena pravdivostní hodnota s informací o tom, zda byl prvek s daným klíčem nalezen či nikoli. Ihned po čtení z mapy tedy můžeme provést explicitní rozeskok:

if exist {
        // prvek byl nalezen
} else {
        // prvek nebyl nalezen
}

Vše si ukážeme v dnešním dvacátém demonstračním příkladu, jehož zdrojový kód vypadá následovně:

package main
 
import "fmt"
 
func main() {
        m1 := make(map[string]int)
        fmt.Println(m1)
 
        m1["nula"] = 0
        m1["jedna"] = 1
        m1["dva"] = 2
        m1["tri"] = 3
        m1["ctyri"] = 4
        m1["pet"] = 5
        m1["sest"] = 6
 
        fmt.Println(m1)
 
        value, exist := m1["dva"]
 
        if exist {
                fmt.Println("Found:", value)
        } else {
                fmt.Println("Not found")
        }
 
        value, exist = m1["sto"]
 
        if exist {
                fmt.Println("Found:", value)
        } else {
                fmt.Println("Not found")
        }
}

Po spuštění by se měly vypsat následující čtyři řádky, přičemž nás samozřejmě zajímají především poslední dvě zprávy:

map[]
map[jedna:1 dva:2 tri:3 ctyri:4 pet:5 sest:6 nula:0]
Found: 2
Not found

10. Vymazání hodnot z map

Do mapy je možné prvky jak přidávat (což již umíme, viz předchozí kapitolu), tak i odebírat. Operace pro odebrání dvojice klíč-hodnota z mapy prošla určitým vývojem, což mj. znamená, že na webu můžeme najít již neplatné návody. V současnosti se pro odebrání položky (přesněji řečeno pro odebrání dvojice klíč-hodnota) používá vestavěná funkce nazvaná poněkud obecně delete. Této funkci se v prvním parametru předává mapa a ve druhém parametru klíč:

delete(mapa, klíč)

Užitečné je, že tato funkce neselže ani ve chvíli, kdy jí předáme neplatný klíč, ani tehdy, pokud se namísto mapy předá hodnota nil. Ostatní kontroly (typ mapy atd.) ovšem překladač samozřejmě stále provádí.

V dnešním dvacátém prvním demonstračním příkladu je ukázáno, jakým způsobem můžeme z již zkonstruované mapy vymazat nějakou dvojici klíč-hodnota. Nejprve vymažeme existující dvojici, poté ovšem zavoláme funkci delete pro neexistující klíč:

package main
 
import "fmt"
 
type Key struct {
        id   uint32
        role string
}
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        m1 := make(map[Key]User)
        fmt.Println(m1)
 
        m1[Key{1, "admin"}] = User{
                id:      1,
                name:    "Pepek",
                surname: "Vyskoč"}
 
        m1[Key{2, "user"}] = User{
                id:      2,
                name:    "Josef",
                surname: "Vyskočil"}
 
        fmt.Println(m1)
 
        delete(m1, Key{2, "user"})
        fmt.Println(m1)
 
        delete(m1, Key{1000, "nekdo jiny"})
        fmt.Println(m1)
}

Na výstupu zobrazeném po spuštění tohoto příkladu je patrné, že druhé volání funkce delete nemělo na výslednou mapu žádný vliv, ovšem současně neskončilo s chybou:

map[]
map[{2 user}:{2 Josef Vyskočil} {1 admin}:{1 Pepek Vyskoč}]
map[{1 admin}:{1 Pepek Vyskoč}]
map[{1 admin}:{1 Pepek Vyskoč}]

11. Ukazatele

Dalším datovým typem, s nímž se dnes alespoň ve stručnosti seznámíme, jsou ukazatele (pointer). Ukazatele mají v Go podobný význam, jako v mnoha dalších jazycích (ostatně byly zařazeny již do Pascalu apod.), ovšem na rozdíl od C/C++ jsou operace s nimi důsledně omezeny a práce s ukazateli je tak v Go bezpečnější. U každého ukazatele je nutné specifikovat, na jaký datový typ ukazuje. Příkladem je ukazatel na hodnotu typu int uloženou někde v operační paměti. Proměnná s ukazatelem musí být typu *int (hvězdičku zde čteme jako „ukazatel na“):

var p_i *int

Implicitní hodnotou přiřazenou do proměnné tohoto typu je nil:

fmt.Println(p_i)

Ukazatel na hodnotu uloženou v paměti se získá unárním operátorem &:

p_i = &i
Poznámka: v jazyce Go se unární operátory & a * zapisují před operand.

V této chvíli je v proměnné p_i uložena adresa paměťové buňky, v níž je uložena hodnota proměnná i. Můžeme si tedy vypsat jak onu adresu, tak i hodnotu přečtenou z této adresy. Pro nepřímé (indirect) čtení přes ukazatel se používá unární operátor *:

fmt.Println(p_i)
fmt.Println(*p_i)

Díky tomu, že výraz *p_i odkazuje na hodnotu uloženou v operační paměti, můžeme s touto hodnotou provádět i další operace, například její zvýšení o jedničku:

*p_i++

Podívejme se nyní na zdrojový kód celého příkladu:

package main
 
import "fmt"
 
func main() {
        var i int = 42
 
        fmt.Println(i)
 
        var p_i *int
        fmt.Println(p_i)
 
        p_i = &i
 
        fmt.Println(p_i)
        fmt.Println(*p_i)
 
        *p_i++
 
        fmt.Println(i)
        fmt.Println(*p_i)
}

Po spuštění tohoto příkladu by se měly zobrazit následující řádky:

42
<nil>
0xc0000140e0
42
43
43
Poznámka: ve skutečnosti bude adresa na třetím řádku odlišná a dokonce se může lišit s každým spuštěním příkladu. Ovšem v praxi nás samotná hodnota adresy prakticky nikdy nebude zajímat (kromě specifických případů, například při přístupu k řídicím registrům na MCU atd.)

12. Ukazatel na strukturu (záznam)

V praxi se poměrně často setkáme s ukazatelem obsahujícím adresu nějaké struktury (záznamu). Takový ukazatel se vytvoří poměrně snadno. V případě, že existuje proměnná u typu User, získáme odkaz na hodnotu v paměti opět s využitím unárního operátoru &:

var u User
 
var p_u *User
p_u = &u

Pokud znáte programovací jazyk C a C++, asi vás napadlo, že přístup k položkám záznamu, na který známe ukazatel, bude prováděn s využitím operátoru ->. Ve skutečnosti tomu tak není; tento operátor Go nezná. Musíme tedy použít buď poměrně nečitelný explicitně zapsaný přístup přes *p_u s tečkovou notací (a s uzavřením do závorek kvůli prioritě operátorů):

(*p_u).id = 10000

Nebo můžeme využít další užitečnou vlastnost Go: automatické dereference při použití tečkového operátoru. Tato dereference je umožněna díky silnému typovému systému:

p_u.id = 20000

Ukažme si nyní obě možnosti na jednoduchém příkladu, v němž vytvoříme záznam, získáme na něj ukazatel a posléze přistoupíme k položce záznamu s využitím tečkového operátoru s explicitní i implicitní dereferencí:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        var u User
 
        u.id = 1
        u.name = "Pepek"
        u.surname = "Vyskoč"
 
        fmt.Println(u)
 
        var p_u *User
        p_u = &u
 
        fmt.Println(p_u)
        fmt.Println(*p_u)
 
        (*p_u).id = 10000
        fmt.Println(*p_u)
 
        p_u.id = 20000
        fmt.Println(*p_u)
}

Výsledek spuštění tohoto příkladu (povšimněte si především druhého řádku – zde se šikovně zobrazí obsah referencovaného záznamu):

{1 Pepek Vyskoč}
&{1 Pepek Vyskoč}
{1 Pepek Vyskoč}
{10000 Pepek Vyskoč}
{20000 Pepek Vyskoč}
Poznámka: bližší informace o všech podporovaných selektorech naleznete na stránce https://golang.org/ref/spec#Se­lectors.

13. Ukazatel na položku záznamu

Bez dalších obsáhlých popisů se podívejme na způsob získání ukazatele na jednu položku záznamu:

package main
 
import "fmt"
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        var u User
 
        u.id = 1
        u.name = "Pepek"
        u.surname = "Vyskoč"
 
        fmt.Println(u)
 
        var p_n *string
        p_n = &u.name
 
        fmt.Println(p_n)
        fmt.Println(*p_n)
 
        (*p_n) = "Zdeněk"
        fmt.Println(*p_n)
}

Tento příklad bude samozřejmě funkční, o čemž se můžeme snadno přesvědčit:

{1 Pepek Vyskoč}
0xc0000840f8
Pepek
Zdeněk

14. Ukazatel na vybraný prvek pole

Získat a použít je možné i ukazatel na vybraný prvek pole:

package main
 
import "fmt"
 
func main() {
        mesice := [12]string{
                "Leden",
                "Únor",
                "Březen",
                "Duben",
                "Květen",
                "Červen",
                "Červenec",
                "Srpen",
                "Září",
                "Říjen",
                "Listopad",
                "Prosinec"}
 
        fmt.Println(mesice)
 
        p_treti := &mesice[2]
        *p_treti = "March"
 
        fmt.Println(mesice)
}

15. Funkce jakožto plnohodnotný datový typ

V programovacím jazyku Go jsou funkce plnohodnotným datovým typem, což v praxi znamená, že funkci (interně referenci na funkci) je možné přiřadit do proměnné, předat do jiné funkce jako parametr atd. Ukažme si nejprve první případ, tj. uložení reference na funkci do lokální proměnné s pozdějším zavoláním této funkce. Proměnná, která je typu funkce bez parametrů a návratové hodnoty, se deklaruje následovně:

var a func()

Do takové proměnné můžeme přiřadit referenci na existující funkci prostým operátorem přiřazení (se jménem funkce se nyní zachází jako s běžnou proměnnou):

func funkce1() {
        fmt.Println("funkce1")
}
 
a = funkce1

A nakonec můžeme funkci zavolat, nikoli ovšem jejím skutečným jménem, ale přes proměnnou a:

a()

Tento způsob práce s funkcemi je ukázán v dalším demonstračním příkladu:

package main
 
import "fmt"
 
func funkce1() {
        fmt.Println("funkce1")
}
 
func funkce2() {
        fmt.Println("funkce2")
}
 
func main() {
        var a func()
        fmt.Println(a)
 
        a = funkce1
        fmt.Println(a)
        a()
 
        a = funkce2
        fmt.Println(a)
        a()
}

Zkusme si nyní tento příklad spustit:

<nil>
0x484b10
funkce1
0x484b80
funkce2

Můžeme vidět, že se nejprve vypíše hodnota nil, tj. výchozí hodnota proměnné typu func(). Posléze se vypíše adresa (reference) na funkci a funkce se zavolá. Tytéž kroky jsou provedeny i s druhou funkcí.

16. Funkce s parametry a její typ

Podobně jako v případě polí, záznamů či ukazatelů je nutné datový typ funkce blíže specifikovat, protože funkce se od sebe liší jak počtem a typem svých parametrů, tak i počtem a typy návratových hodnot (prozatím pro jednoduchost nebudeme uvažovat funkce s proměnným počtem parametrů). Ukažme si příklad s funkcí, která bude mít dva parametry typu celé číslo (int) a jednu návratovou hodnotou:

func funkce1(x int, y int) int {
        return x + y
}

Typ této funkce je func(int, int) int a proměnnou tohoto typu nadeklarujeme snadno:

var a func(int, int) int

Po přiřazení reference skutečné funkce můžeme proměnnou a použít pro její volání:

fmt.Println(a(10, 20))

Opět se podívejme na úplný demonstrační příklad:

package main
 
import "fmt"
 
func funkce1(x int, y int) int {
        return x + y
}
 
func funkce2(x int, y int) int {
        return x * y
}
 
func main() {
        var a func(int, int) int
        fmt.Println(a)
 
        a = funkce1
        fmt.Println(a)
        fmt.Println(a(10, 20))
 
        a = funkce2
        fmt.Println(a)
        fmt.Println(a(10, 20))
}

Po spuštění tohoto příkladu dostaneme těchto pět řádků se zprávami:

<nil>
0x484b90
30
0x484bb0
200
Poznámka: to, že je typ funkce úplný až po specifikaci typu parametrů a návratových hodnot, si můžeme snadno ověřit pokusem o překlad dalšího příkladu:
package main
 
import "fmt"
 
func funkce1(x int) int {
        return 2 * x
}
 
func funkce2(x int, y int) int {
        return x * y
}
 
func main() {
        var a func(int) int
        fmt.Println(a)
 
        a = funkce1
        fmt.Println(a)
        fmt.Println(a(10))
 
        a = funkce2
        fmt.Println(a)
        fmt.Println(a(10, 20))
}

V tomto příkladu se snažíme o přiřazení funkce se dvěma parametry typu int do proměnné, jejíž typ je funkce s jedním parametrem. Tato programátorská chyba je samozřejmě opět odhalena už překladačem:

./28_improper_func_type.go:21:4: cannot use funkce2 (type func(int, int) int) as type func(int) int in assignment
./28_improper_func_type.go:23:15: too many arguments in call to a
        have (number, number)
        want (int)

17. Definice vlastního datového typu funkce s návratovou hodnotou

V dnešním posledním demonstračním příkladu si ukážeme, jak lze vytvořit nový uživatelský datový typ představující referenci na funkci určitého typu. Pro tento účel opět použijeme klíčové slovo type:

type no_param_function func() int
type two_int_param_function func(int, int) int

Alternativně můžeme obě definice sloučit do jediného bloku, což se v Go děje poměrně často:

type (
        no_param_function      func() int
        two_int_param_function func(int, int) int
)

Deklarace proměnných nových typů je již snadná:

var a no_param_function
var b two_int_param_function

Opět si ukažme celý příklad:

package main
 
import "fmt"
 
type no_param_function func() int
type two_int_param_function func(int, int) int
 
func funkce1() int {
        return 42
}
 
func funkce2() int {
        return -1
}
 
func funkce3(x int, y int) int {
        return x + y
}
 
func funkce4(x int, y int) int {
        return x * y
}
 
func main() {
        var a no_param_function
        var b two_int_param_function
 
        fmt.Println(a)
        fmt.Println(b)
 
        a = funkce1
        fmt.Println(a)
        fmt.Println(a())
 
        a = funkce2
        fmt.Println(a)
        fmt.Println(a())
 
        b = funkce3
        fmt.Println(b)
        fmt.Println(b(10, 20))
 
        b = funkce4
        fmt.Println(b)
        fmt.Println(b(10, 20))
}

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:

# Demonstrační příklad Popis Cesta
1 01_user_types.go definice jednoduchých uživatelských typů https://github.com/tisnik/go-fedora/blob/master/article03/01_u­ser_types.go
2 02_type_func_params.go funkce akceptující uživatelské typy https://github.com/tisnik/go-fedora/blob/master/article03/02_ty­pe_func_params.go
3 03_typed_array.go pole obsahující prvky uživatelského typu https://github.com/tisnik/go-fedora/blob/master/article03/03_ty­ped_array.go
4 04_typed_array_B.go vylepšení předchozího příkladu https://github.com/tisnik/go-fedora/blob/master/article03/04_ty­ped_array_B.go
5 05_user_type_checks.go kontrola při přetypování https://github.com/tisnik/go-fedora/blob/master/article03/05_u­ser_type_checks.go
6 06_type_check_func.go kontrola typů při volání funkce https://github.com/tisnik/go-fedora/blob/master/article03/06_ty­pe_check_func.go
7 07_typed_array_check.go přiřazení polí (bez chyby) https://github.com/tisnik/go-fedora/blob/master/article03/07_ty­ped_array_check.go
8 08_typed_array_check.go pokus o přiřazení polí odlišného typu https://github.com/tisnik/go-fedora/blob/master/article03/08_ty­ped_array_check.go
9 09_struct.go základ práce se záznamy https://github.com/tisnik/go-fedora/blob/master/article03/09_struc­t.go
10 10_struct_init.go deklarace a inicializace záznamu https://github.com/tisnik/go-fedora/blob/master/article03/10_struc­t_init.go
11 11_better_struct_init.go explicitní inicializace položek záznamu https://github.com/tisnik/go-fedora/blob/master/article03/11_bet­ter_struct_init.go
12 12_struct_comparison.go porovnání dvou záznamů https://github.com/tisnik/go-fedora/blob/master/article03/12_struc­t_comparison.go
13 13_array_of_structs.go pole záznamů https://github.com/tisnik/go-fedora/blob/master/article03/13_a­rray_of_structs.go
14 14_array_of_structs.go inicializace pole záznamů https://github.com/tisnik/go-fedora/blob/master/article03/14_a­rray_of_structs.go
15 15_uninitialized_map.go pokus o použití neinicializované mapy https://github.com/tisnik/go-fedora/blob/master/article03/15_u­ninitialized_map.go
16 16_initialized_map.go korektní inicializace mapy https://github.com/tisnik/go-fedora/blob/master/article03/16_i­nitialized_map.go
17 17_initialized_map.go využití operátoru := https://github.com/tisnik/go-fedora/blob/master/article03/17_i­nitialized_map.go
18 18_map_and_struct.go mapa se strukturami https://github.com/tisnik/go-fedora/blob/master/article03/18_map_an­d_struct.go
19 19_map_and_struct_B.go mapa se strukturami (složitější varianta) https://github.com/tisnik/go-fedora/blob/master/article03/19_map_an­d_struct_B.go
20 20_reading_from_maps.go čtení hodnot z mapy https://github.com/tisnik/go-fedora/blob/master/article03/20_re­ading_from_maps.go
21 21_delete_from_map.go vymazání hodnot z mapy https://github.com/tisnik/go-fedora/blob/master/article03/21_de­lete_from_map.go
22 22_basic_pointers.go základní použití ukazatelů https://github.com/tisnik/go-fedora/blob/master/article03/21_ba­sic_pointers.go
23 23_pointer_to_struct.go ukazatel na strukturu https://github.com/tisnik/go-fedora/blob/master/article03/22_po­inter_to_struct.go
24 24_pointer_to_struct_item.go ukazatel na položku struktury https://github.com/tisnik/go-fedora/blob/master/article03/24_po­inter_to_struct_item.go
25 25_pointer_to_array_item.go ukazatel na prvek pole https://github.com/tisnik/go-fedora/blob/master/article03/25_po­inter_to_array_item.go
26 26_func_type.go datový typ funkce bez parametrů https://github.com/tisnik/go-fedora/blob/master/article03/26_fun­c_type.go
27 27_func_type.go datový typ funkce s parametry https://github.com/tisnik/go-fedora/blob/master/article03/27_fun­c_type.go
28 28_improper_func_type.go kontrola datového typu funkce https://github.com/tisnik/go-fedora/blob/master/article03/28_im­proper_func_type.go
29 29_func_type.go datový typ funkce s návratovou hodnotou https://github.com/tisnik/go-fedora/blob/master/article03/29_fun­c_type.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
  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/
Našli jste v článku chybu?