Programovací jazyk GO

Pavel Tišnovský

Vydavatel:

CZ.NIC, z. s. p. o.

Milešovská 5, 130 00 Praha 3

Edice CZ.NIC

www.nic.cz

1. vydání, Praha 2025

Kniha vyšla jako 34. publikace v Edici CZ.NIC.

ISBN 978-80-88168-82-9

© 2025 Pavel Tišnovský

Toto autorské dílo podléhá licenci Creative Commons BY-ND 4.0 (creativecommons.org/licenses/by-nd/4.0/). Dílo však může být překládáno a následně šířeno v písemné či elektronické formě, na území kteréhokoliv státu, za předpokladu, že nedojde ke změně díla a i nadále zůstane zachováno označení autora a prvního vydavatele díla, sdružení CZ.NIC, z. s. p. o. Překlad může být šířen pod licencí CC BY-ND 4.0.

Tato kniha vyšla v Edici CZ.NIC. Chcete přispět na vznik dalších? Darujte libovolnou částku na dar.nic.cz/kniha-go

Edice CZ.NIC je jednou z osvětových aktivit sdružení CZ.NIC, správce české národní domény.

ISBN 978-80-88168-84-3

— Pavel Tišnovský

Programovací jazyk GO

— Edice CZ.NIC

Obsah

Předmluva vydavatele

Předmluva

1

Úvod

1.1

Autoři jazyka Go

1.2

Go nebo Golang?

2

Jazyk Go v současnosti

2.1

Podman

2.2

Kubernetes

2.3

MinIO

3

Go a další programovací jazyky

3.1

Od Pythonu ke Go?

3.2

Go z pohledu vývojáře

3.3

Go není „pouze“ vylepšený programovací jazyk C

3.4

Automatická správa paměti

3.5

Minimalisticky pojatý programovací jazyk

4

Instalace překladače jazyka Go i dalších podpůrných nástrojů

4.1

Instalace v operačním systému Microsoft Windows

4.2

Instalace v operačním systému Linux

4.3

Kontrola, zda je Go nainstalován korektně

5

První kroky s Go

5.1

Způsob zobrazení zdrojových kódů programů v této knize

5.2

Struktura zdrojových kódů

5.3

Program typu „Hello, world!“

5.4

Překlad a spuštění programu

5.5

Důležité nástroje, které jsou součástí instalace Go

6

Základy programovacího jazyka Go

6.1

Klíčová slova

6.2

Operátory a znaky se speciálním významem

6.3

Základní datové typy

6.4

Konstanty

6.5

Proměnné

6.6

Automatické odvození datových typů proměnných

6.7

Funkce

6.8

Řízení toku programu

6.9

Rozvětvení

6.10

Programové smyčky

6.11

Návrat do minulosti: příkaz goto

6.12

Konstrukce defer

7

Standardní datové typy

7.1

Hierarchie typového systému

7.2

Společné vlastnosti standardních datových typů

7.3

Celočíselné datové typy

7.4

Hodnoty s plovoucí řádovou čárkou

7.5

Hodnoty představující komplexní číslo

7.6

Pravdivostní hodnoty

8

Složené datové typy

8.1

Řetězce

8.2

Pole

8.3

Řezy

8.4

Mapy

8.5

Záznamy

9

Hodnota nil

10

Ukazatele

10.1

Základní vlastnosti ukazatelů v jazyce Go

10.2

Použití ukazatelů

11

Souběžné a paralelní výpočty

11.1

Procesy, vlákna, korutiny a gorutiny

11.2

Tvorba a spouštění gorutin

11.3

Kanály – technologie pro komunikaci mezi gorutinami

11.4

Deadlock a jeho detekce v runtime

11.5

Konstrukce select

12

Objektově orientované programování

12.1

Metody

12.2

Rozhraní (interface)

12.3

Splnění rozhraní

12.4

Řez se strukturami implementujícími společné rozhraní

13

Generické datové typy

13.1

Beztypové a jednoúčelové kontejnery

13.2

Statická a dynamická genericita

13.3

Řešení problematiky generických datových typů v dalších jazycích

13.4

Funkce s konkrétními datovými typy vs. s generickými typy

13.5

Použití prázdných rozhraní, která splňují jakýkoli datový typ

13.6

Novinka v Go 1.18: typové parametry

13.7

Datový systém jazyka Go a přetížené operátory

13.8

Problematika odvozených datových typů a aproximace datových typů

13.9

Typové parametry v roli datových typů v těle funkce

13.10

Typová množina odvozená od jiné typové množiny

13.11

Generické funkce a přístup k prvkům struktur

13.12

Generické datové typy a kontejnery

14

Síťové nástroje a služby

14.1

Realizace přenosu dat po síti

14.2

Pomocné síťové funkce

14.3

Balíček http

14.4

Šablony HTML stránek

14.5

Využití protokolu HTTPS namísto HTTP

15

Testování aplikací

15.1

Testování jakožto samostatný obor IT

15.2

Další zkratky z oblasti testování v IT

15.3

Tvorba testů v jazyce Go

15.4

Implementace jednotkových testů

15.5

Mockování funkcí a metod pro potřeby jednotkových testů

15.6

Export jmen privátních funkcí

16

Reflexe

16.1

Rozhraní ve funkci plnohodnotného datového typu

16.2

Rozlišení konkrétních typů v čase běhu programu

16.3

Typové aserce

16.4

Rozvětvení provedené na základě běhové typové informace

16.5

Standardní balíček reflect

16.6

Reflexe a hodnoty nil

16.7

Přečtení informace o typu

17

Jazyk Go ve webovém prohlížeči

17.1

Aplikace běžící ve webovém prohlížeči

17.2

Technologie WebAssembly

17.3

Podpora WebAssembly v jazyce Go

17.4

Transpřekladač z jazyka Go do JavaScriptu

17.5

Předávání argumentů mezi Go a JavaScriptem

17.6

Vrácení hodnot z Go do volajícího programu v JavaScriptu

18

Go v roli skriptovacího programovacího jazyka

18.1

Specifické oblasti použití skriptovacích jazyků

18.2

Gore

18.3

Yaegi

18.4

Gomacro

18.5

Shrnutí

19

Systém modulů

19.1

Balíčky, moduly a repositáře

19.2

Vytvoření prvního jednoduchého modulu

19.3

Verzování balíčků

19.4

Vytvoření externí knihovny s několika majoritními verzemi a jednou verzí minoritní

19.5

Použití externí knihovny s více majoritními verzemi v demonstračním projektu

19.6

Sémantické importy

19.7

Shrnutí

20

Závěr

Odkazy na další informační zdroje

Předmluva vydavatele

Předmluva vydavatele

Po knize Evoluce Pythonu, která nabídla pohled na vývoj jednoho z nejrozšířenějších programovacích jazyků, přichází autor s novým titulem zaměřeným na jazyk Go. Tentokrát se soustředí na praktický vývoj backendových aplikací, ale opět zůstává u stylu, který jde dál než jen k samotným pravidlům jazyka: snaží se postihnout širší kontext a vývojové souvislosti.

Přestože jazyk Go a Python jsou v mnoha ohledech odlišné, autor přistupuje k tématu podobně – věcně, s důrazem na srozumitelnost a využitelnost v praxi. Text není psán jako učebnice, ale spíš jako průvodce pro vývojáře, kteří hledají praktické odpovědi i prostor pro vlastní úvahy.

Jsme rádi, že můžeme tuto knihu nabídnout čtenářům, kteří se o jazyk Go zajímají nebo s ním již pracují, a zároveň si cení otevřeného pohledu, který autor k tématu přináší.

Za celý tým Edice CZ.NIC přejeme příjemné čtení.

Praha, 19. června 2025

Předmluva

Předmluva

V současnosti se mohou vývojáři setkat s celou řadou programovacích jazyků. Tyto aktivně používané programovací jazyky se od sebe v mnoha oblastech odlišují a taktéž se nasazují v různých oblastech IT. Mezi jeden z nejpopulárnějších programovacích jazyků současnosti patří i programovací jazyk nazvaný Go, s jehož možnostmi se podrobněji seznámíme v této knize. Jedná se o výkonný kompilovaný a taktéž o silně typovaný programovací jazyk, který je navržen takovým způsobem, že základy tohoto jazyka je možné se naučit doslova za několik hodin a již za přibližně týden či čtrnáct dní je v něm možné tvořit aplikace určené pro produkční nasazení. V současnosti tvoří aplikace a nástroje vyvinuté v jazyku Go jádro většiny komponent ekosystému tzv. kontejnerů. V Go je naprogramován například Kubernetes, OpenShift, Podman, Docker, Prometheus, ale i další populární nástroje, mezi které patří NATS, MinIO či NSQ.

1

Úvod

1

Úvod

V současnosti se můžeme ve vývojářské praxi setkat s celou řadou programovacích jazyků. Tyto aktivně používané programovací jazyky se od sebe pochopitelně v mnoha oblastech odlišují a taktéž se nasazují v různých oblastech IT (některé na frontendu, další spíše na backendu, v oblasti datové analýzy atd.). Mezi jeden z nejpopulárnějších programovacích jazyků současnosti patří i programovací jazyk nazvaný Go, s jehož možnostmi se podrobněji seznámíme v této knize.

Jedná se o výkonný kompilovaný (překládaný) a taktéž o silně typovaný programovací jazyk, který je navržen takovým způsobem, že základy tohoto jazyka je možné se naučit doslova za několik hodin a již za přibližně týden či čtrnáct dní je v něm možné tvořit aplikace určené pro produkční nasazení. Taktéž se jedná o jazyk, jenž je určen pro vývoj služeb (resp. možná přesněji řečeno mikroslužeb) popř. nástrojů ovládaných z příkazového řádku. V současnosti tvoří aplikace a nástroje vyvinuté v jazyku Go jádro většiny komponent ekosystému tzv. kontejnerů (containers). Konkrétně to znamená, že v Go je naprogramován například Kubernetes, OpenShift, Podman, Docker, Prometheus, ale i další populární nástroje, mezi které patří NATS, Minio či NSQ.

1.1

Autoři jazyka Go

Go při svém představení vzbudil ohlas nejenom kvůli svým technickým vlastnostem, ale i díky tomu, že pochází z „dílny“ Google a taktéž proto, že za jeho designem stojí mj. i dvě slavné osobnosti z oblasti informatiky: Rob „Commander“ Pike a Ken Thompson.

1.2

Go nebo Golang?

Oficiální název tohoto programovacího jazyka je Go. To je ovšem poměrně nešťastně zvolené jméno, protože Go je jak název známé deskové hry, tak i běžné slovo v angličtině. To způsobuje problémy při vyhledávání dalších informací o tomto jazyku a mj. i z tohoto důvodu se poměrně často setkáme i s označením Golang (Go language). V této knize se ovšem budeme držet oficiálního jména Go.

2

Jazyk Go v současnosti

2

Jazyk Go v současnosti

V současnosti nalezneme aplikace a služby psané v jazyce Go především na serverech, tedy na back endu. V Go je naprogramována poměrně velká část služeb souvisejících s kontejnery, ale taktéž některé databáze (například CockroachDB), různé formy message brokerů atd. Pro zajímavost se o některých těchto technologiích alespoň ve stručnosti zmíníme.

2.1

Podman

Podman neboli pod manager je nástroj určený pro správu kontejnerů. Využívá knihovnu libpod a nabízí rozhraní (API) pro správu životního cyklu kontejnerů, jednotlivých podů, obrazů (image) i svazků. Důležité je, že rozhraní Podmanu je kompatibilní s rozhraním známého systému Docker, takže administrátoři, kteří znají Docker, mohou na Podmana snadno přejít. A podobně jako Docker nabízí Docker Desktop je možné v případě systému Podman využívat Podman Desktop.

2.2

Kubernetes

Kubernetes je systém určený pro takzvanou orchestraci virtualizace prováděnou na úrovni operačního systému. Původně jej vyvinula společnost Google (ta stála i za vývojem samotného Go) a jako podřízené nástroje podporuje například známý Docker ale i výše zmíněný Podman. S využitím Kubernetes je možné spouštět vybrané a nakonfigurované úlohy v takzvaných kontejnerech. Díky tomu se velmi často používá v cloudových řešeních, datových centrech atd.

2.3

MinIO

Velmi úspěšným projektem napsaným v jazyce Go je MinIO. Jedná se o sadu několika služeb a nástrojů, které uživatelům poskytují distribuované datové úložiště určené pro ukládání obecných (nestrukturovaných) dat. Typicky se jedná o soubory používané v oblasti AI (Artifical Intelligence) a ML (Machine Learning), ovšem kromě těchto populárních (a vlastně do značné míry i módních) oblastí IT je pochopitelně možné službu MinIO použít i pro ukládání logů, souborů, k nimž je zapotřebí rychle přistupovat z mnoha různých, mnohdy vzájemně vzdálených oblastí (zde využijeme možnost distribuovaného systému), jako centrální úložiště dokumentů, obrázků, videí, pochopitelně i obrazů souborových systémů pro Docker apod. MinIO dosahuje velmi slušné rychlosti přístupu k datům (při vhodně nadimenzované síti, která je většinou limitujícím faktorem) a mj. i díky velmi dobré stabilitě ukazuje přednosti programovacího jazyka Go, ve kterém je celý systém naprogramován.

Jedním z nejdůležitějších a v důsledku i nejpraktičtějších vlastností projektu MinIO je fakt, že se pro přístup k datům používá stejná technologie, jaká je implementována i v populární službě Amazon S3 či možná přesněji AWS S3. To mj. znamená, že dodávaný MinIO Client SDK může sloužit jak pro přístup k datům uloženým v Miniu, tak i k datům uloženým ve cloudu na S3. Díky tomu lze například snadněji nastavit konfiguraci pro vývoj, konfiguraci CI, zajistit si možnost využití veřejného cloudu (S3) nebo naopak privátního cloudu (založeného na Miniu) atd.

2.3.1

NATS

Systém NATS patří mezi takzvané message brokery, což jsou systémy určené pro doručování, ukládání a redistribuci zpráv. NATS je poměrně úspěšný projekt, jenž je vyvinut právě v programovacím jazyce Go, což mu do značné míry zajišťuje stabilitu i škálovatelnost. To jsou ostatně vlastnosti, které u message brokerů většinou očekáváme. NATS v současnosti poměrně zdárně konkuruje dalším message brokerům, mezi které patří například RabbitMQ, ActiveMQ (Artemis), IBM MQ atd.

2.3.2

NATS JetStream

Součástí projektu NATS je i technologie nazvaná NATS JetStream. S jejím využitím lze realizovat takzvaný streaming zpráv s podobnými vlastnostmi, jaké nalezneme u „konkurenčního“ projektu Apache Kafka. NATS JetStream používá klasický systém (server) NATS, který navíc doplňuje o takzvaný storage, tj. o technologii určenou pro ukládání zpráv (někdy nazývaných i záznamy – record) do perzistentního úložiště, kterým může být relační databáze či soubor resp. skupina souborů.

2.3.3

NSQ

V jazyce Go je vytvořen i další message broker nazvaný nsq. Ten se skládá z několika samostatně spouštěných uzlů, které mezi sebou vhodným způsobem komunikují. Výhodou a jednou ze základních vlastností nsq je fakt, že způsob jeho návrhu počítá s tím, že jednotlivé uzly mohou být po nějakou dobu nedostupné a přitom systém jako celek by měl být stále použitelný. Jedná se tedy o architekturu vhodnou pro nasazení do dnešního hybridní prostředí se systémy běžícími v cloudu, lokálně v kontejneru, na dedikovaných serverech atd.

2.3.4

FZF

Jedním z nástrojů psaných v Go, které se těší poměrně značné popularitě, je utilita nazvaná fzf neboli „command-line fuzzy finder“. Základní funkce fzf je ve skutečnosti velmi jednoduchá a vlastně i typicky „unixovská“. Nástroj fzf totiž na svém standardním vstupu (stdin) získá libovolný seznam, který následně zobrazí a nechá uživatele vybrat jednu položku z tohoto seznamu. Výsledek následně předá na svůj standardní výstup (stdout) pro další zpracování. To pravděpodobně nezní moc bombasticky ani příliš užitečně, ovšem samotný výběr položky ze seznamu je plně interaktivní a podporuje takzvaný „fuzzy“ výběr (snadno použitelný je i pro ty uživatele, kteří neznají regulární výrazy). A především – největší síla fzf spočívá v tom, že tento nástroj je možné kombinovat s vybranými standardními příkazy popř. dalšími utilitami, všude tam, kde je nutné vybrat hodnotu ze seznamu (platného například v nějakém kontextu).

3

Go a další programovací jazyky

3

Go a další programovací jazyky

Programovací jazyk Go a jeho základní knihovny byly původně navrženy takovým způsobem, aby se v Go daly efektivně psát různé síťové aplikace, které by ideálně neměly trpět potenciálními výkonnostními problémy nebo bezpečnostními dírami typu „buffer overflow“ atd. Právě z toho důvodu, že síťové a serverové aplikace musí v typické konfiguraci obsluhovat velké množství souběžně prováděných požadavků, byly do jazyka Go přidány již v úvodní kapitole zmíněné gorutiny a kanály. A nutno říci, že Go se v této oblasti skutečně velmi často používá. Taktéž se předpokládalo, že se jazyk Go bude používat pro vývoj těch aplikací, které jsou v současnosti psány v jazycích C či C++ (možná i v Rustu). Tento přechod sice skutečně částečně nastal, ovšem prozatím ne v příliš velké míře (a to se bavíme o použití v serverových a desktopových aplikacích, protože v oblasti mikrořadičů se s jazykem Go sice laboruje, ale především u relativně výkonných MCU typicky založených na čipech Cortex-M, nikoli na malých osmibitových a šestnáctibitových mikrořadičích).

3.1

Od Pythonu ke Go?

Několik let po představení tohoto jazyka však nastala zajímavější situace, o níž jsme se opět krátce zmínili v úvodu – programovací jazyk Go a jeho relativní snadnost použití a poskytované možnosti objevili vývojáři, kteří psali své síťové a speciálně pak webové aplikace v Pythonu, Ruby či JavaScriptu/TypeScriptu. Přepis takových aplikací do jazyka Go mnohdy znamenal řádový a někdy i dvouřádový (tedy 100× a více) nárůst výkonu těchto aplikací. Do jisté míry je nárůst výkonu způsobem překladem do nativního kódu, ovšem nesmíme zapomenout na gorutiny, které nejsou (na rozdíl od klasických vláken) příliš náročné na paměť, takže se můžeme setkat s aplikacemi, v nichž bez větších problémů běží stovky či dokonce tisíce gorutin (což si ostatně ještě ukážeme v prakticky zaměřené části této knihy).

3.2

Go z pohledu vývojáře

Go je staticky typovaný programovací jazyk a programy, které jsou v něm napsány, jsou kompilovány (překládány) do nativního kódu (někdy se setkáme s přesnějším označením strojový kód neboli machine code). Použití statických datových typů vede k tvorbě bezpečnějšího kódu a společně s výše uvedeným konceptem překladu do strojového kódu jsou výsledné aplikace velmi rychlé, zejména v porovnání s aplikacemi naprogramovanými například v Pythonu či v dalších interpretovaných jazycích.

Programovací jazyk Go navíc programátorům nabízí i koncept výše zmíněných gorutin a kanálů používaných při komunikaci mezi gorutinami. Díky tomu lze velmi snadno a především bezpečně psát aplikace, jejichž části běží souběžně (concurrently) či dokonce paralelně.

3.3

Go není „pouze“ vylepšený programovací jazyk C

Mezi autory návrhu i první implementace programovacího jazyka Go patří mj. i Ken Thompson. Když si uvědomíme, že Ken vytvořil i (dnes prehistorický) programovací jazyk B, který je předchůdcem slavného jazyka C, mohlo by se zdát, že Go bude pouhým vylepšením programovacího jazyka C (například nějaká obdoba jazyka C++). Je sice pravda, že Go alespoň částečně z programovacího jazyka C skutečně vychází, ovšem z hlediska syntaxe jsou si mnohem bližší například jazyky C a Java, než C a Go.

Tvůrci programovacího jazyka Go se navíc snažili poučit se z některých problematických rysů jazyků C a taktéž C++. Z tohoto důvodu navrhli Go takovým způsobem, aby byla jeho syntaxe jednodušší a snáze pochopitelná (sami se po prohlédnutí zdrojových kódů můžete rozhodnout, do jaké míry se to splnilo). Samotný jazyk je velmi konzervativní a někteří kritici dokonce (s nadsázkou) tvrdí, že zcela ignoruje všechny novinky, které se na poli programovacích jazyků za posledních dvacet let objevily.

3.4

Automatická správa paměti

Navíc se tvůrci jazyka Go zaměřili i na další typické problémy, s nimiž se musí vypořádat prakticky všichni programátoři používající jazyky C, C++ a vlastně i uživatelé Rustu (ti nepřímo): jedná se o problematiku správy paměti a uvolňování zdrojů (resources) a v neposlední řadě i o podporu pro paralelní běh částí programů, například částí komunikujících přes síť, pracujících se souborovým systémem atd. Z tohoto důvodu byly přímo do jazyka Go přidány dvě důležité technologie: automatický správce paměti a již výše zmíněné gorutiny (goroutines). S oběma těmito technologiemi se podrobněji seznámíme v navazujících kapitolách.

Poznámka: na tomto místě je vhodné si uvědomit, že použití automatického správce paměti přenáší část problémů (které by jinak musel řešit programátor) do takzvaného runtime, což se může negativně projevit na výkonu aplikace (zpomalení aplikace, větší nároky na kapacitu haldy, musí se počítat i s okamžiky, kdy je aplikace pozastavena apod.). Vlivem správce paměti na výkon se budeme zabývat v samostatné kapitole, takže zde jen bez dalších důkazů uveďme, že automatický správce paměti implementovaný v jazyku Go nemá větší negativní vliv na dobu startu aplikací (ostatně, nemusí se například spouštět ani virtuální stroj ani inicializovat interpret), spotřeba paměti je však průměrně větší, než v případě C/C++, ale na druhou stranu menší, než je tomu při porovnání s obdobnými aplikacemi psanými v Javě.

3.5

Minimalisticky pojatý programovací jazyk

Některé technologie naopak do jazyka Go přidány nebyly, a to z toho důvodu, aby byl jazyk snadno použitelný a navíc i snadno implementovatelný. Právě fakt, že se v Go zpočátku neobjevily dnes již široce akceptované technologie, jako je obsluha výjimek či práce s generickými datovými typy, byl poměrně často kritizován; ovšem Go se postupně mění a přidávají se do něj další vlastnosti. Ovšem tyto nové vlastnosti jsou přidávány relativně pomalu a poměrně konzervativním způsobem, což je podle názoru autora této knihy v této oblasti jen dobře. Poslední velkou změnou je přidání podpory pro generické datové typy.

4

Instalace překladače jazyka Go i dalších podpůrných nástrojů

4

Instalace překladače jazyka Go i dalších podpůrných nástrojů

I když existují různá webová „pískoviště“, ve kterých je možné si vyzkoušet základy programovacího jazyka Go, je pro výuku tohoto jazyka výhodnější přímá instalace všech základních nástrojů Go, tj. jak vlastního překladače, tak i nástrojů pro naformátování zdrojových kódů, instalaci modulů, prohlížení nápovědy atd. Instalace Go se pochopitelně liší podle toho, jaký operační systém používáte. Z tohoto důvodu je popis instalace rozdělen do několika částí – instalace Go na systému Microsoft Windows a instalace na vybraných distribucích Linuxu.

4.1

Instalace v operačním systému Microsoft Windows

Instalace nástrojů jazyka Go pro operační systém Microsoft Windows je snadná a poměrně rychlá. Postačuje přejít na stránku go.dev/. Kromě dalších informací zde nalezneme tlačítka Get Started a Download. Stiskneme tlačítko Download a po přechodu na další stránku v sekci nazvané Featured downloads (zhruba v polovině obrazovky) nalezneme i instalátor nástrojů Go pro systém Microsoft Windows. V době psaní této knihy se jedná o soubor go1.24.2.windows-amd64.msi, ovšem číslo verze se pochopitelně bude postupně zvyšovat. Tento soubor je nutné stáhnout a spustit. Další kroky instalace jsou snadné – instalátor se spustí, ponechají se všechna výchozí nastavení a tlačítkem Next se postupně instalace dokončí. Následně si spusťte konzoli (postačuje cmd.exe) a v ní napište příkaz:

go

V případě, že instalace proběhla v pořádku, měla by se vypsat nápověda k nástrojům jazyka Go.

4.2

Instalace v operačním systému Linux

Instalace jazyka Go v operačním systému Linux se liší v závislosti na tom, jakou konkrétní distribuci používáte a jaký má tato distribuce systém pro správu balíčků. V současnosti se používá několik správců balíčků, ovšem nejčastěji se pravděpodobně setkáte se systémem nazvaným yum (Yellowdog Updater Modified) resp. jeho následovníkem dnf, nebo s nástrojem apt (Advanced Package Tool). Z tohoto důvodu byly pro účely popisu instalace Go vybrány dva typy distribucí Linuxu. Do první skupiny patří Fedora, do skupiny druhé pak Ubuntu a Linux Mint.

4.2.1

Instalace do distribuce Fedora

Popišme si nyní instalaci základních nástrojů programovacího jazyka Go do distribuce Fedora. Instalace bude na všech třech posledních vydáních Fedory prakticky totožná, takže se zaměřme na Fedoru 40, pro niž byl vytvořen balíček s Go verze 1.23.8 (což je v současnosti stabilní verze tohoto jazyka). Instalaci provedeme buď přímo z terminálu s přihlášeným superuživatelem (rootem) příkazem:

dnf install golang

Alternativně samozřejmě můžeme použít instalaci s využitím nástroje sudo, pokud má ovšem přihlášený uživatel příslušná práva:

sudo dnf install golang

V obou případech by měla instalace proběhnout prakticky totožným způsobem:

==========================================

Package

Architecture

Version

Repository

Size

==========================================

Installing:

golang

x86_64

1.23.8-1.fc40

updates

667 k

Installing dependencies:

go-filesystem

x86_64

3.5.0-1.fc40

fedora

8.8 k

golang-bin

x86_64

1.23.8-1.fc40

updates

27 M

golang-src

noarch

1.23.8-1.fc40

updates

13 M

libserf

x86_64

1.3.10-5.fc40

fedora

59 k

subversion-libs

x86_64

1.14.4-1.fc40

updates

1.5 M

Installing weak dependencies:

mercurial

x86_64

6.7.4-3.fc40

updates

6.4 M

python3-fb-re2

x86_64

1.0.7-15.fc40

fedora

23 k

subversion

x86_64

1.14.4-1.fc40

updates

1.0 M

Transaction Summary

==========================================

Install  9 Packages

Total download size: 50 M

Installed size: 239 M

Is this ok [y/N]:

Po odpovědi „Y“ se instalace rozběhne a pochopitelně se nijak zásadně neliší od instalace jakéhokoli jiného balíčku:

Downloading Packages:

(1/9): go-filesystem-3.5.0-1.fc40.x86_64.rpm   113 kB/s | 8.8 kB   00:00

...

...

...

Running transaction

Running scriptlet: golang-1.23.8-1.fc40.x86_64   1/1

...

...

...

Installed:

go-filesystem-3.5.0-1.fc40.x86_64   golang-1.23.8-1.fc40.x86_64   golang-bin-1.23.8-1.fc40.x86_64

golang-src-1.23.8-1.fc40.noarch   libserf-1.3.10-5.fc40.x86_64   mercurial-6.7.4-3.fc40.x86_64

python3-fb-re2-1.0.7-15.fc40.x86_64   subversion-1.14.4-1.fc40.x86_64   subversion-libs-1.14.4-1.fc40.x86_64

Complete!

Povšimněte si, že se po nainstalování rozbalí přibližně 239 MB dat, což je sice poměrně velká hodnota, ovšem je vhodné si uvědomit, že se kromě samotného překladače jazyka Go nainstalují i další podpůrné nástroje (formátovač zdrojového kódu atd.), celá základní knihovna Go, zdrojové kódy základní knihovny (ty slouží pro čtení dokumentace) atd. Samotný ekosystém jazyka Go se všemi svými nástroji vyžaduje necelých 300 MB diskového prostoru.

4.2.2

Ubuntu a Mint

V distribucích Ubuntu a Mint se jako správce balíčků používá nástroj apt (Advanced Package Tool). Instalaci Go můžeme provést buď jako administrátor (root) příkazem:

apt install golang

nebo lze, jako na Fedoře, použít příkaz sudo, ovšem za předpokladu, že jste součástí skupiny uživatelů, kteří mohou získat administrátorská práva:

sudo apt install golang

Průběh instalace probíhá zhruba následovně:

Reading package lists... Done

Building dependency tree... Done

Reading state information... Done

The following additional packages will be installed:

golang-1.23 golang-1.23-doc golang-1.23-go golang-1.23-src golang-doc

golang-go golang-src

Suggested packages:

bzr | brz mercurial subversion

The following NEW packages will be installed:

golang golang-1.23 golang-1.23-doc golang-1.23-go golang-1.23-src

golang-doc

golang-go golang-src

0 upgraded, 8 newly installed, 0 to remove and 623 not upgraded.

Need to get 82,5 MB of archives.

After this operation, 438 MB of additional disk space will be used.

4.2.3

Lokální instalace

V případě, že nemáte práva administrátora (a ani možnost použít příkaz sudo) lze instalaci nástrojů jazyka Go provést i lokálně – pouze pro přihlášeného uživatele. Opět přejděte na stránku go.dev/, na níž nalezneme tlačítka Get Started a Download. Stiskneme tlačítko Download a po přechodu na další stránku v sekci nazvané Featured downloads nalezneme i balíček s Go pro Linux (takzvaný tarball). V době psaní této knihy se jedná o soubor se jménem go1.24.2.linux-amd64.tar.gz. Ten stáhneme a přesuneme do vhodného adresáře, například do domovského adresáře uživatele. V tomto adresáři stačí otevřít terminál (konzoli) a napsat příkaz pro rozbalení staženého tarballu:

tar xvfz go1.24.2.linux-amd64.tar.gz

Po rozbalení vznikne adresář nazvaný go. Nastavte si cestu do podadresáře go/bin:

PATH=go/bin:$PATH

Nyní by měl být dostupný příkaz go i další příkazy ekosystému jazyka Go.

4.3

Kontrola, zda je Go nainstalován korektně

Po instalaci základních nástrojů programovacího jazyka Go je vhodné si otestovat, jestli jsou tyto nástroje skutečně použitelné. Většina nabízených funkcí je dostupná přes „univerzální“ příkaz go. V případě, že na terminál (konzoli) zapíšeme pouze tento příkaz bez dalších doplňujících informací, měla by se vypsat krátká nápověda:

Go is a tool for managing Go source code.

Usage:

go <command> [arguments]

The commands are:

bug

start a bug report

build

compile packages and dependencies

clean

remove object files and cached files

doc

show documentation for package or symbol

env

print Go environment information

fix

update packages to use new APIs

fmt

gofmt (reformat) package sources

generate

generate Go files by processing source

get

add dependencies to current module and install them

install

compile and install packages and dependencies

list

list packages or modules

mod

module maintenance

work

workspace maintenance

run

compile and run Go program

test

test packages

tool

run specified go tool

version

print Go version

vet

report likely mistakes in packages

Use "go help <command>" for more information about a command.

Additional help topics:

buildconstraint build constraints

buildmode

build modes

c

calling between Go and C

cache

build and test caching

environment

environment variables

filetype

file types

go.mod

the go.mod file

gopath

GOPATH environment variable

goproxy

module proxy protocol

importpath

import path syntax

modules

modules, module versions, and more

module-auth

module authentication using go.sum

packages

package lists and patterns

private

configuration for downloading non-public code

testflag

testing flags

testfunc

testing functions

vcs

controlling version control with GOVCS

Use "go help <topic>" for more information about that topic.

4.3.1

Ověření verze jazyka Go

Pro začátek si vypíšeme, která verze jazyka Go je vlastně aktuálně dostupná. K tomuto účelu slouží následující varianta již výše zmíněného příkazu go:

go version

V případě, že jste si nainstalovali Go verze 1.23.3 na 64bitové architektuře x86-64 (známé též pod označením AMD64), měla by se na plochu terminálu vypsat následující zpráva:

go version go1.23.3 linux/amd64

Při instalaci starší verze Go se pochopitelně vypíše odlišná verze jazyka. Opět si uveďme příklad:

go version go1.22.1 linux/amd64

V této kapitole jsme si ukázali instalaci jazyka Go ve verzi 1.23 popř. 1.24. Ve skutečnosti budou všechny demonstrační příklady pracovat i ve starší verzi 1.22 (a mnohé i ve starších verzích, teoreticky i verze 1.9 atd.). Go je v tomto ohledu velmi konzervativní a i když se jazyk postupně vyvíjí, zachovává si zpětnou kompatibilitu.

4.3.2

Proměnné prostředí používané jazykem Go

V některých situacích je nutné si ověřit konfiguraci Go. Pro zobrazení všech proměnných, které ovlivňují chování Go, se používá příkaz:

go env

GO111MODULE=''

GOARCH='amd64'

GOBIN=''

GOCACHE='/home/ptisnovs/.cache/go-build'

GOENV='/home/ptisnovs/.config/go/env'

GOEXE=''

GOEXPERIMENT=''

GOFLAGS=''

GOHOSTARCH='amd64'

GOHOSTOS='linux'

GOINSECURE=''

GOMODCACHE='/home/ptisnovs/go/pkg/mod'

GONOPROXY=''

GONOSUMDB=''

GOOS='linux'

GOPATH='/home/ptisnovs/go'

Samozřejmě je možné si nechat vypsat jen jednu konkrétní hodnotu, například:

go env GOPATH

/home/ptisnovs/go

5

První kroky s Go

5

První kroky s Go

V této kapitole se seznámíme se základními vlastnostmi programovacího jazyka Go. Ukážeme si také, jakým způsobem se zdrojové kódy překládají a spouští. Předpokládá se, že již máte nainstalovány základní nástroje jazyka Go (viz předchozí kapitolu). Pro tvorbu a úpravy zdrojových kódů je možné použít jakýkoli (programátorský) textový editor popř. integrované vývojové prostředí (VSCode apod.). Můžete taktéž použít webové „pískoviště“ dostupné na adrese go.dev/play/. Pro první seznamování s Go může být toto pískoviště dostačující (navíc nevyžaduje žádné instalace), ale pro vážně pojatý vývoj se nehodí.

5.1

Způsob zobrazení zdrojových kódů programů v této knize

Ve zdrojových kódech programů, které jsou napsány v jazyce Go, se pro odsazení programových řádků používají ASCII znaky s kódem 9. Tyto znaky se do zdrojových kódů vkládají klávesou Tab neboli tabulátorem. V programátorských textových editorech popř. v integrovaných vývojových prostředích je většinou možné si nastavit, jakým způsobem má být znak s ASCII kódem 9 zobrazen. Typicky se používají čtyři mezery nebo osm mezer (méně často například jen dvě mezery, to ovšem nedoporučuji). V této knize z důvodu úspory místa používáme čtyři mezery namísto obvyklejších osmi mezer. Ovšem v repositáři se všemi ukázkovými příklady (github.com/tisnik/programovaci-jazyk-Go) je korektně použit již zmíněný ASCII znak 9.

5.2

Struktura zdrojových kódů

Zdrojové kódy vytvářené v jazyce Go mají do určité míry pevnou strukturu, se kterou se nyní seznámíme.

5.2.1

Jméno balíčku

Každý soubor obsahující zdrojový kód musí začínat klíčovým slovem package, za nímž následuje jméno balíčku, kterého je tento kód součástí. Naše první programy budou velmi jednoduché a budou se skládat z jediného balíčku, který se jmenuje main, což znamená, že první řádek našich zdrojových kódu bude vypadat následovně:

package main

Poznámka: ve skutečnosti se mohou ještě před tímto řádkem nacházet textové řádky s komentáři nebo prázdné řádky. Ovšem první skutečný programový řádek začíná právě slovem package.

5.2.2

Deklarace funkcí

Ve zdrojových kódech dále nalezneme deklarace dalších „součástek“, ze kterých se program skládá. V první řadě se jedná o deklaraci funkcí. Deklarace funkce se skládá z hlavičky funkce (header) následovaného tělem funkce (body). Každá deklarace nové funkce přitom začíná klíčovým slovem func, za nímž následuje jméno funkce. V kulatých závorkách jsou zapsána jména a typy parametrů funkce a za závorkami pak typ návratové hodnoty popř. typy (a dokonce i volitelné názvy) návratových hodnot. Samotné tělo funkce je vždy zapsáno mezi složené závorky {}, přičemž otevírací levá složená závorka musí být vždy zapsána na stejném textovém řádku, jako hlavička funkce (pozor – jiné programovací jazyky nejsou až takto striktní).

Jméno funkce musí začínat podtržítkem nebo písmenem a může obsahovat jak znaky (prakticky jakékoli) abecedy, tak i číselné cifry či podtržítka. Pozor ovšem na to, že jméno tvořené jediným podtržítkem _ má v Go speciální význam.

Nejjednodušší funkce nemají žádné parametry, takže namísto nich uvedeme pouze prázdné kulaté závorky. Na rozdíl od některých jiných programovacích jazyků tedy nepoužíváme „prázdný“ typ void. V té nejjednodušší podobě funkce ani nevrací žádnou hodnotu a její tělo je prázdné. Deklarace takové funkce by měla mohla vypadat následovně:

func jmenoFunkce() {

}

5.2.3

Komentáře ve zdrojových kódech

Do zdrojových kódů je pochopitelně možné přidávat i komentáře. V tomto ohledu programovací jazyk Go používá podobné zásady zápisu komentářů, jaké jsou použité v programovacích jazycích syntakticky vycházejících z jazyka C (sem spadá C++, ale vlastně i Java či JavaScript). Jednořádkové komentáře začínají dvojicí znaků //, ovšem zapisovat je možné i víceřádkové komentáře. V takovém případě komentář začíná znaky /* a končí znaky */. Ukažme si několik jednoduchých příkladů.

Jednořádkové komentáře, které končí na tom samém řádku, na němž jsou zapsány:

// jméno balíčku

package main // i toto je komentář

// funkce main představuje v jazyce Go

// vstupní bod do aplikace

func main() { // začátek těla funkce

}

Komentář pokračující přes větší množství řádků a komentář na jediném řádku (který však může být později rozšířen):

/*

Jméno balíčku, do kterého zdrojový kód patří.

Balíček main má specifický význam pro běžné programy, ovšem v knihovnách se nepoužívá.

*/

package main

/* Funkce main, která slouží jako vstupní bod do aplikace. */

func main() {

}

Zakomentovat můžeme i část zdrojového kódu, což může být využito při ladění atd.:

package main

/* Funkce main, která slouží jako vstupní bod do aplikace. */

func main() {

}

// Zakomentovaná část zdrojového kódu

/*

func main() {

}

*/

Ovšem pozor na to, že víceřádkové komentáře není možné „zanořit“ do jiného komentáře. Následující příklad tedy nebude přeložen:

package main

/* Funkce main, která slouží jako vstupní bod do aplikace. */

func main() {

}

// Zakomentovaná část zdrojového kódu

/*

func main() {

/* vložený komentář */

}

*/

5.2.4

Funkce main

Již v předchozích příkladech jsme specifikovali jméno balíčku main. Dále ve zdrojovém kódu programu s využitím klíčového slova func nadeklarujeme funkci nazvanou taktéž main (což může být zpočátku matoucí, ale rychle si zvyknete). Takto pojmenovaná funkce, která je současně vstupním bodem do aplikace, nemá žádné parametry ani návratovou hodnotu (více se o návratových hodnotách dozvíme v navazujících kapitolách). Minimalisticky pojatý, ovšem zcela funkční program napsaný v jazyku Go tedy bude vypadat následovně:

package main

func main() {

}

5.3

Program typu „Hello, world!“

Podívejme se nyní na nepatrně složitější program. Samozřejmě začneme klasickým programem typu „Hello, world!“, což je tradice, která již před několika desetiletími byla zavedena v učebnici programovacího jazyka C. Konkrétně si na standardní výstup necháme vypsat zprávu „Hello, world!“ a poté program ukončíme. Nejprve si ukažme, jak bude zdrojový kód takového programu vypadat a posléze si popíšeme jeho jednotlivé části:

// První kroky v jazyku Go

//

// - program typu "Hello, world!" ve variantě naprogramované

//   v jazyku Go.

// specifikace jména balíčku (zde musíme použít "main")

package main

// import konstant, typů a funkcí z jiného balíčku

import "fmt"

// deklarace funkce nazvané main (vstupní bod do programu)

func main() {

// volání funkce náležející do jiného balíčku

// s využitím takzvané tečkové notace

fmt.Println("Hello, world!")

}

5.3.1

Anatomie programu typu „Hello, world!“

Zdrojový kód našeho programu si můžeme rozdělit do několika logických částí, a to následujícím způsobem:

1.

Jméno balíčku

2.

Jméno jiného balíčku, který importujeme

3.

Definice funkce

Povšimněte si jedné důležité a důsledně dodržované vlastnosti programovacího jazyka Go – každá uvedená logická část vždy začíná nějakým klíčovým slovem, což je slovo, které má v Go speciální a pevně daný význam. Díky této vlastnosti je usnadněno čtení zápisů zdrojových kódů, a to jak pro programátory, tak i pro počítač (konkrétně pro překladač a další nástroje zpracovávající zdrojové kódy). V mnoha dalších programovacích jazycích není tento koncept použit, popř. je použit jen částečně.

Vraťme se však k oněm třem logickým částem zdrojového kódu. Programový řádek s názvem balíčku již známe; jedná se o klíčové slovo package následované jménem, které se může skládat z písmen, znaku podtržítka a číslic, ovšem nesmí začínat číslicí. Řádek obsahující klíčové slovo import obsahuje jméno jiného balíčku, jehož konstanty, datové typy a funkce budeme chtít volat. V tomto případě je skutečně nutné takový balíček nejdříve „naimportovat“. V praxi se setkáme s tím, že je nutné importovat větší množství balíčků. To lze provést buď pomocí více příkazů importu:

// První kroky v jazyku Go

//

// - import symbolů (funkcí atd.) ze dvou balíčků

// - každý balíček je naimportován přes samostatný příkaz "import"

package main

// import jediného balíčku

import "fmt"

// import jediného balíčku

import "os"

func main() {

// zde můžeme používat funkce z obou naimportovaných balíčků

fmt.Println("Hello, world!")

os.Exit(0)

}

Doporučený zápis je však odlišný: více názvů balíčků se v tomto případě zapíše za klíčové slovo import do kulatých závorek (skutečně se jedná o kulaté závorky – zde se poměrně často dělá chyba):

// První kroky v jazyku Go

//

// - import symbolů (funkcí atd.) ze dvou balíčků

// - oba balíčky jsou naimportovány jediným příkazem "import"

package main

// import dvou balíčků v jediném bloku "import"

import (

"fmt"

"os"

)

func main() {

// zde můžeme používat funkce z obou naimportovaných balíčků

fmt.Println("Hello, world!")

os.Exit(0)

}

Poznámka: převod skupiny příkazů import na blok většinou dokážou provést programátorské editory zcela automaticky. Moderní editory taktéž seznam balíčků abecedně seřadí. Pokud vám toto řazení nevyhovuje, vložte mezi jména balíčků prázdné řádky.

Nejsložitější je třetí logická část zdrojového kódu naší aplikace, která obsahuje deklaraci funkce. Z hlavičky funkce je zřejmé, že se jedná o funkci bez parametrů a bez návratových hodnot. A tělo funkce obsahuje volání jediné další funkce, konkrétně funkce se jménem Println z balíčku fmt. Podrobnosti si řekneme v dalších kapitolách, ovšem již nyní je vhodné poznamenat, že z jiných balíčků lze volat jen ty funkce, jejichž název začíná velkým písmenem. A tento požadavek funkce Println nepochybně splňuje.

5.4

Překlad a spuštění programu

Náš základní demonstrační příklad, jehož zdrojový kód je uložený v souboru hello_world.go, si můžeme spustit a to konkrétně příkazem go run hello_world.go. Před samotným spuštěním se sice (většinou zcela na pozadí) provede překlad, přičemž spustitelná verze programu je uložena do dočasného souboru, ovšem po ukončení programu se tento dočasný soubor smaže, takže se nám může zdát, jako bychom zdrojový kód přímo spouštěli (tj. Go se zdánlivě tváří jako interpret, i když jsou zdrojové kódy vždy překládány):

go run hello_world.go

Hello, world!

5.4.1

Překlad do nativního spustitelného souboru

V případě, že si budeme přát program explicitně přeložit a zachovat výsledný binární spustitelný soubor, použijeme namísto příkazu go run příkaz go build, a to konkrétně tímto způsobem:

go build hello_world.go

Výsledkem překladu bude soubor, který bude pojmenovaný stejně jako soubor zdrojový, ovšem s odlišnou koncovkou. Na Linuxu nebude koncovka použita vůbec:

ls -1

total 1880

-rwxr-xr-x. 1 ptisnovs ptisnovs 1894201 Dec  8 17:16 hello_world

-rw-r--r--. 1 ptisnovs ptisnovs     330 Dec  7 09:48 hello_world.go

V operačním systému Microsoft Windows je výsledkem překladu soubor s koncovkou .exe, tedy konkrétně hello_world.exe.

5.4.2

Vlastnosti souboru získaného překladem

V obou případech obsahuje spustitelný soubor, který jsme získali překladem, jak nativní kód naší přeložené aplikace, tak i všechny potřebné funkce nutné pro spuštění této aplikace. Tyto funkce tvoří takzvaný runtime a mj. zde nalezneme inicializační rutiny, ale například i celý subsystém pro automatickou správu paměti (včetně garbage collectoru). Právě kvůli tomu, že se v tomto binárním souboru nachází i všechny potřebné funkce pro runtime, je soubor relativně velký – jeho velikost se blíží ke dvěma megabajtům, zatímco aplikace se stejnou funkcionalitou naprogramovaná v jazyku C bude mít po překladu velikost 6 až 14 kB v závislosti na použité architektuře i na použitém překladači a linkeru. Na druhou stranu bude náš hello_world prakticky bez problémů přenositelný i na další počítače se stejnou architekturou a stejným operačním systémem (k této problematice se ještě vrátíme).

Právě přeloženou aplikaci si můžeme velmi snadno spustit následujícími příkazy. Na Linuxu:

./hello_world

Hello, world!

Na systému Microsoft Windows (z příkazové řádky):

hello_world.exe

V případě, že budeme chtít prozkoumat, jaké symboly se vlastně v souboru 01_hello_world; nachází (je jich zhruba 2000, protože je zde skutečně celý runtime), můžeme na Lixuxu použít příkaz objdump:

hello_world:     file format elf64-x86-64

SYMBOL TABLE:

0000000000000000 l   df *ABS*  0000000000000000 go.go

0000000000401000 l    F .text  0000000000000000 runtime.text

0000000000402d20 l    F .text  000000000000022d cmpbody

0000000000402f70 l    F .text  0000000000000153 memeqbody

0000000000403120 l    F .text  0000000000000129 indexbytebody

000000000045f600 l    F .text  0000000000000040 gogo

000000000045f640 l    F .text  000000000000002b callRet

000000000045f680 l    F .text  000000000000002f gosave_systemstack_switch

000000000045f6c0 l    F .text  000000000000000d setg_gcc

000000000045f6d0 l    F .text  0000000000000564 aeshashbody

...

...

...

5.5

Důležité nástroje, které jsou součástí instalace Go

Kromě samotného překladače jazyka Go, který jsme použili v předchozím textu, je součástí instalace Go i celá řada dalších užitečných nástrojů. O těch nejdůležitějších se nyní ve stručnosti zmíníme.

5.5.1

Zobrazení dokumentace

Dokumentace k jazyku Go i ke standardním knihovnám je umístěna na adrese godoc.org/. V předchozích ukázkových příkladech jsme mj. používali i balíček nazvaný fmt, který je součástí standardní knihovny jazyka Go a jehož dokumentaci samozřejmě můžeme taktéž nalézt na výše zmíněných stránkách popsán. Konkrétně je dokumentace tohoto balíčku dostupná na adrese godoc.org/fmt.

Kromě toho je však možné si nechat dokumentaci zobrazit přímo v terminálu (konzoli) při vývoji. K tomuto účelu slouží příkaz go doc, kterému se jako další parametr předá jméno balíčku popř. přímo jméno funkce, jejíž popis potřebujeme získat:

go doc fmt

Výsledek by měl vypadat následovně:

package fmt // import "fmt"

Package fmt implements formatted I/O with functions analogous to C's printf and

scanf. The format 'verbs' are derived from C's but are simpler.

# Printing

The verbs:

General:

%v  the value in a default format

when printing structs, the plus flag (%+v) adds field names

...

...

...

Poznámka: celkem se vypíše přibližně 380 řádků nápovědy.

Zobrazit si ovšem můžeme i nápovědu ke konkrétní funkci z balíčku fmt, a to následujícím způsobem:

go doc fmt.Println

V tomto případě je výsledek poměrně stručný:

package fmt // import "fmt"

func Println(a ...any) (n int, err error)

Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.

5.5.2

Doporučovaný styl zápisu programů

Tvůrci programovacího jazyka Go navrhli, jakým způsobem se mají formátovat zdrojové kódy. Nezůstalo ovšem pouze u doporučení, protože přímo v základní sadě nástrojů jazyka Go nalezneme i utilitu nazvanou příznačně gofmt. Tento nástroj slouží k přeformátování zdrojových kódů takovým způsobem, aby jejich výsledné naformátování odpovídalo specifikaci. Díky tomu mají (nebo by alespoň měly mít) všechny zdrojové kódy napsané v jazyku Go jednotný standardní formát a například diskuze o použití tabulátorů nebo mezer, popř. o kolik znaků (mezer) se má provést odsazení bloků, přestávají mít smysl.

Ve zkratce autoři Go vlastně říkají: namísto dlouhých popisů standardů formátování zdrojových kódů, které známe například z PEP-8 (Python), GNU Coding Standards či Linux kernel coding style se v případě jazyka Go formátováním programátoři příliš zabývat nemusí, protože za ně většinu práce odvede nástroj gofmt, který má navíc (z definice) vždycky pravdu.

Poznámka: naprosto stejné formátování nabízí i běžná integrovaná vývojová prostředí a programátorské editory.

5.5.3

Nástroj gofmt určený pro přeformátování zdrojových kódů

Pokud jste provedli instalaci Go a jeho nástrojů podle pokynů ze čtvrté kapitoly, bude nástroj gofmt jednoduše použitelný:

gofmt --help

Tento nástroj podporuje různé volby, ovšem v praxi využijete pravděpodobně především volbu -w pro zápis přeformátovaného zdrojového kódu do jiného souboru:

usage: gofmt [flags] [path ...]

-cpuprofile

string write cpu profile to this file

-d

display diffs instead of rewriting files

-e

report all errors (not just the first 10 on different lines)

-l

list files whose formatting differs from gofmt's string

-r

rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')

-s

simplify code

-w

write result to (source) file instead of stdout

Zkusme například formátovači předat následující zdrojový kód se středníky, špatným odsazením, prázdnými řádky atd.:

package main

import "fmt";

func main() {

fmt.Println("Hello, playground");

}

Po naformátování získáme tento obsah, který plně odpovídá specifikaci:

package main

import "fmt"

func main() {

fmt.Println("Hello, playground")

}

6

Základy programovacího jazyka Go

6

Základy programovacího jazykaGo

Vtéto kapitole se seznámíme se základními rysy iněkterými specifickými vlastnostmi programovacího jazyka Go, takže po jejím přečtení již budete schopni tvořit jednodušší aplikace běžící vjediné gorutině. Ovšem pro plné využití možností, které jsou jazykem Go nabízeny, bude nutné prostudovat idalší rysy tohoto jazyka– zejména se jedná ovelmi důležitý koncept datových typů, použití rozhraní (interface), využití výše zmíněných gorutin akanálů atd.

Všechny dále popisované vlastnosti programovacího jazyka Go pochopitelně budou prakticky předvedeny vcelé řadě demonstračních příkladů. Tyto příklady byly otestovány vGo verze 1.23, ovšem naprostá většina příkladů je použitelná ive starších verzích tohoto jazyka. Na ty ukázkové příklady, kde tomu tak není (například při popisu některé nejnovější vlastnosti Go), budete pochopitelně upozorněni.

6.1

Klíčová slova

Ve specifikaci programovacího jazyka Go je definováno celkem dvacet pět rezervovaných klíčových slov (vangličtině keywords). Jedná se oslova, která mají ve zdrojových kódech speciální význam anelze je tedy použít pro pojmenování balíčků, konstant, funkcí, metod ani proměnných– vtakovém případě překladač ohlásí syntaktickou chybu. Všechna rezervovaná klíčová slova jazyka Go jsou vypsána pod tímto odstavcem apostupně se sjejich významem pochopitelně seznámíme:

break

case

chan

const

continue

default

defer

else

fallthrough

for

func

go

goto

if

import

interface

map

package

range

return

select

struct

switch

type

var

Poznámka: zajímavé je, že ikdyž se samotný jazyk Go postupně rozšiřuje, například ogenerické datové typy nebo onové varianty programových smyček, samotný seznam klíčových slov se prozatím nerozšířil. Ztohoto pohledu je tedy zachována přísná zpětná kompatibilita.

Jen pro zajímavost si vyzkoušejme, co se stane vpřípadě, že se pokusíme použít klíčová slova vjiném významu, konkrétně jako jméno proměnné. Takto použijeme klíčová slova type ainterface:

// Základy programovacího jazyka Go

//

// - pokus opoužití klíčových slov ve funkci identifikátorů

//

// POZOR: tento příklad nebude možné přeložit

package main

func main() {

// pokus opoužití klíčového slova "interface"

// jako identifikátoru při deklaraci konstanty

const interface int = 10

// pokus opoužití klíčového slova "type"

// jako identifikátoru při deklaraci proměnné

var type string = "Škoda 100"

}

Pokus opřeklad tohoto zdrojového kódu nebude (podle očekávání) úspěšný:

./01_01_keywords_as_identifiers.go:10:8: syntax error: unexpected interface, expected name

./01_01_keywords_as_identifiers.go:11:6: syntax error: unexpected type, expected name

6.1.1

Jména standardních datových typů nejsou klíčovými slovy

Za povšimnutí stojí, že mezi rezervovanými klíčovými slovy nenajdeme jména standardních datových typů, mezi než patří bool, int, float32 či string. Askutečně: jména standardních datových typů nejsou ve standardu Go rezervována, což znamená, že můžeme například napsat následující program, ve kterém je změněn význam typu int (resp.přesněji řečeno se změní význam identifikátoru int):

// Základy programovacího jazyka Go

//

// - deklarace datového typu nazvaného "int", který přepíše původní typ "int"

//

// POZOR: tento způsob není doporučený aje dobré vědět, kdy ho použít

// apředevším, kdy ho naopak nepoužít.

package main

import "fmt"

// nový datový typ "int", který vtomto balíčku přepíše původní typ

// se stejným jménem

type int = float32

func main() {

// proměnná nazvaná "pi" je sice typu "int", ovšem na rozdíl od jména

// typu se jedná onumerickou hodnotu splovoucí řádovou čárkou

var pi int

// nyní můžeme do proměnné přiřadit hodnotu splovoucí řádovou čárkou

// ikdyž se (zdánlivě) jedná oproměnnou celočíselného typu

pi = 3.14

fmt.Println(pi)

}

!

Pozor: vtomto případě se jedná pouze oukázku, jak by se programy vpraxi psát neměly. Tímto způsobem se totiž celý program znepřehlední anavíc bude mít jméno datového typu odlišný význam vrůzných balíčcích.

6.1.2

Pravdivostní konstanty nejsou klíčovými slovy

Mezi rezervovanými klíčovými slovy jazyka Go nenajdeme dokonce ani pravdivostní konstanty true afalse, což opět znamená, že můžeme (ale nikdy bychom to dělat neměli) změnit jejich význam. Opět si uveďme příklad, jak bychom neměli psát programy:

// Základy programovacího jazyka Go

//

// - deklarace proměnné "true", která přepíše původní hodnotu "true"

//

// POZOR: tento způsob není doporučený aje dobré vědět, kdy ho použít

// apředevším, kdy ho naopak nepoužít.

package main

import "fmt"

func main() {

// deklarace proměnné nazvané "true", tím vtomto balíčku ztratíme

// přístup kpůvodní hodnotě true (což většinou nechceme!)

var true string = "pravda"

// deklarace proměnné, která je ve skutečnosti typu "string"

// (překladač nyní použije hodnotu proměnné "true")

var answer string = true

fmt.Println(answer)

}

Poznámka: podobně je tomu shodnotou nil, sjejímž významem se seznámíme při popisu ukazatelů arozhraní.

6.2

Operátory aznaky se speciálním významem

Ve specifikaci programovacího jazyka Go taktéž můžeme najít tabulku se všemi operátory (operators) adalšími znaky se speciálním významem. Podívejme se, jaké jednotlivé znaky nebo jejich kombinace překladač programovacího jazyka Go rozpoznává:

+

&

+=

&=

&&

==

!=

(

)

-

|

-=

|=

||

<

<=

[

]

*

^

*=

^=

<-

>

>=

{

}

/

<<

/=

<<=

++

=

:=

,

;

%

>>

%=

>>=

--

!

...

.

:

&^

&^=

~

Opět platí, že tyto kombinace znaků mají svůj pevně daný význam, který není možné žádným způsobem měnit. Navíc jazyk Go nepodporuje ani přetěžování operátorů (operator overloading).

6.2.1

Tabulka všech operátorů

Vpřípadě, že se zaměříme pouze na operátory (anikoli na speciální znaky), tabulka zpředchozího odstavce se zmenší na:

+

&

+=

&=

&&

==

!=

-

|

-=

|=

||

<

<=

*

^

*=

^=

<-

>

>=

/

<<

/=

<<=

++

=

%

>>

%=

>>=

--

!

&^

&^=

Pro čtenáře této knihy pravděpodobně nebude žádnou novinkou, že operátory určené pro manipulaci sčíselnými, pravdivostními, řetězcovými aj.hodnotami, tvoří podstatnou část syntaxe vprakticky všech mainstreamových programovacích jazycích. Vprogramovacím jazyku Go nalezneme celkem 26 základních operátorů, knimž navíc ještě musíme připočíst operátory spojené spřiřazením (tedy například přičtení hodnoty namísto obecnějšího součtu atd.):

Skupina operátorů

Operátory

aritmetické

+ - * / %

aritmetické spřiřazením

+= -= *= /= %=

logické

&& || !

posuny abitové operace

<< >> & | ^ &^

posuny abitové operace spřiřazením

<<= >>= &= |= ^= &^=

relační

== != < <= > >=

operace sadresami (unární)

* &

další unární operátory

+ - ^

speciální operátory

<- := ++ --

Poznámka: ve skutečnosti má operátor + ještě další význam. Kromě součtu dvou numerických hodnot totiž realizuje ispojení (konkatenaci) dvou řetězců; není tedy svázán jen snumerickými datovými typy.

Vnavazujících podkapitolách si ukážeme význam většiny operátorů zpředchozí tabulky.

6.2.2

Priority operátorů

Zajímavá je tabulka sprioritami operátorů. Ta určuje, jakým způsobem se budou vyhodnocovat výrazy světším množstvím operátorů, například výraz a+b*c. Vjazyce Go je tabulka sprioritami operátorů poměrně jednoduchá, zejména vporovnání stabulkami, které někteří čtenáři pravděpodobně znají zjazyků C, C++ či Javy. Nejvyšší prioritu mají prakticky všechny unární operátory (sjediným operandem zapisovaným za operátor) anásledně existuje pouze pět priorit, které si můžete zapamatovat svyužitím mnemotechnické pomůcky MACAO:

Úroveň

Mnemotechnická pomůcka

Operátory

1

Multiplicative

* / % << >> & &^

2

Additive

+ - | ^

3

Comparison

== != < <= > >=

4

And

&&

5

Or

||

Poznámka: některé operátory, zejména ++ a--, stojí mimo tuto tabulku. Je tomu tak iztoho důvodu, že tyto operátory vystupují vždy vroli příkazu anikoli jako součást výrazu (příkaz je vykonán, kdežto výraz vrací hodnotu).

6.2.3

Unární operátory

Nejprve se podrobněji podívejme na použití unárních operátorů. Ty mají, jak již víme, nejvyšší prioritu aproto se téměř nikdy nesetkáme snutností uzavírat tyto operátory do kulatých závorek, které (podobně jako vmnoha dalších programovacích jazycích aostatně ivmatematice) slouží ke změně priority. Vjazyce Go nalezneme tyto unární operátory:

Operátor

Význam operátoru

+

nemění znaménko výsledku (opak dalšího operátoru)

-

mění znaménko výsledku

!

logická negace

^

negace bit po bitu (podobně jako  ~ vC)

*

přístup na adresu přes ukazatel (referenci)

&

získání adresy operandu

<-

přečtení hodnoty zkanálu

Ukažme si nyní, jakým způsobem se jednotlivé unární operátory používají. Povšimněte si, že se všechny zmíněné operátory vždy zapisují před operand, tj. na levou stranu od operandu:

// Základy programovacího jazyka Go

//

// - unární operátory definované vjazyku Go

package main

import "fmt"

func main() {

// unární operátory + a-

var iint = 42

fmt.Println(+i)

fmt.Println(-i)

// unární operátor ^

i= 0

fmt.Println(^i)

i++

fmt.Println(^i)

// unární operátor !

var b bool = false

fmt.Println(!b)

// unární operátor &

fmt.Println(&i)

// unární operátory & a*

var ptrI *int = &i

fmt.Println(*ptrI)

// unární operátor <-

var channel chan int

channel = make(chan int)

<-channel

}

Tento příklad je pouze ilustrační, protože po spuštění skončí schybou při pokusu očtení hodnoty zkanálu channel sčekáním (viz další kapitoly):

42

-42

-1

-2

true

0xc000124010

1

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:

main.main()

/home/ptisnovs/Documents/Go/priklady/06_zaklady/02_01_unary_operators.go:33 +0x24f

exit status 2

6.2.4

Základní aritmetické operátory

Vprogramovacím jazyce Go nalezneme všech pět základních binárních aritmetických operátorů (binárních proto, že akceptují dva operandy– levý apravý). Jedná se ooperátory určené pro realizaci operace součtu, rozdílu, součinu, podílu azbytku po dělení. Těchto pět operátorů můžeme použít sdvojicí celočíselných operandů tak, jak je to naznačeno vdalším ukázkovém příkladu:

// Základy programovacího jazyka Go

//

// - základní aritmetické operátory použité sproměnnými

// typu int

package main

import "fmt"

func main() {

// dvojice proměnných typu int

var x int = 10

var y int = 3

// výpis původních hodnot proměnných

fmt.Printf("x = %d\n", x)

fmt.Printf("y = %d\n", y)

fmt.Println()

// výpis výsledků aritmetických operací sproměnnými

fmt.Printf("x+y = %d\n", x+y)

fmt.Printf("x-y = %d\n", x-y)

fmt.Printf("x*y = %d\n", x*y)

fmt.Printf("x/y = %d\n", x/y)

fmt.Printf("x%%y = %d\n", x%y)

}

Výsledky:

x = 10

y = 3

x+y = 13

x-y = 7

x*y = 30

x/y = 3

x%y = 1

Uhodnot splovoucí řádovou čárkou (floating point numbers) není kdispozici operátor pro výčet zbytku po dělení, takže je možné provádět jen součet, rozdíl, součin apodíl:

// Základy programovacího jazyka Go

//

// - základní aritmetické operátory sproměnnými

// typu float32

package main

import "fmt"

func main() {

// dvojice proměnných typu float32

var x float32 = 1.1

var y float32 = 2.2

// výpis původních hodnot proměnných

fmt.Printf("x = %f\n", x)

fmt.Printf("y = %f\n", y)

fmt.Println()

// výpis výsledků aritmetických operací sproměnnými

fmt.Printf("x+y = %f\n", x+y)

fmt.Printf("x-y = %f\n", x-y)

fmt.Printf("x*y = %f\n", x*y)

fmt.Printf("x/y = %f\n", x/y)

}

Výsledky:

x = 1.100000

y = 2.200000

x+y = 3.300000

x-y = -1.100000

x*y = 2.420000

x/y = 0.500000

Stejná čtveřice operátorů, tedy součet, rozdíl, součin apodíl, je použita ipro komplexní čísla, ikdyž součin apodíl se interně počítá poněkud komplikovanějším způsobem:

// Základy programovacího jazyka Go

//

// - základní aritmetické operátory sproměnnými

// typu complex64

package main

import "fmt"

func main() {

// dvojice proměnných typu complex64

var x complex64 = 1 + 1i

var y complex64 = 2 + 2i

// výpis původních hodnot proměnných

fmt.Printf("x = %v\n", x)

fmt.Printf("y = %v\n", y)

fmt.Println()

// výpis výsledků aritmetických operací sproměnnými

fmt.Printf("x+y = %v\n", x+y)

fmt.Printf("x-y = %v\n", x-y)

fmt.Printf("x*y = %v\n", x*y)

fmt.Printf("x/y = %v\n", x/y)

}

Opět si ukažme výsledky:

x = (1+1i)

y = (2+2i)

x+y = (3+3i)

x-y = (-1-1i)

x*y = (0+4i)

x/y = (0.5+0i)

Jak jsme již mohli vidět, vprogramovacím jazyce Go se pro zápis podílu používá operátor / apro výpočet zbytku po dělení operátor %. Ostatně podobně je tomu ivprakticky všech programovacích jazycích více či méně odvozených od jazyka C. Vyzkoušejme si nyní, jak vlastně dělení avýpočet zbytku probíhá pro kladné izáporné dělence adělitele, protože chování pro záporné hodnoty nemusí být příliš intuitivní. Pomůže nám tento demonstrační příklad:

// Základy programovacího jazyka Go

//

// - operátory pro celočíselný podíl avýpočet zbytku po dělení

// - použití pro různé kombinace kladných izáporných operandů

package main

import "fmt"

// Výpočet podílu azbytku po dělení

func computeDivMod(x, y int) {

fmt.Printf("%3d / %2d = %3d %3d %% %2d = %3d\n", x, y, x/y, x, y, x%y)

}

func main() {

// otestování výpočtu podílu azbytku po dělení pro všechny

// kombinace kladných azáporných hodnot dělenců adělitelů

computeDivMod(10, 3)

computeDivMod(-10, 3)

computeDivMod(10, -3)

computeDivMod(-10, -3)

fmt.Println()

// tabulka svýsledky zbytku po dělení 100/i

// pro hodnoty ivrozsahu od 1 do 10

for i:= 1; i<= 10; i++ {

computeDivMod(100, i)

}

}

Vprogramovacím jazyce Go je znaménko zbytku odvozeno od znaménka dělence (tedy prvního operandu). Toto chování je zachováno na všech platformách, zatímco vjazycích C aC++ je implementačně závislé.

VGo tedy dostaneme tyto výsledky:

10/ 3= 3 10% 3= 1

-10/ 3= -3 -10% 3= -1

10/-3= -3 10%-3= 1

-10/-3= 3 -10%-3= -1

Ztoho ovšem vyplývají idalší vlastnosti, například to, že se při celočíselném dělení kladných čísel zaokrouhluje výsledek směrem dolů. Je tomu tak ztoho důvodu, že zbytek musí být vtomto případě buď nulový nebo kladný. Opět si to ověřme:

100/ 1=100 100% 1= 0

100/ 2= 50 100% 2= 0

100/ 3= 33 100% 3= 1

100/ 4= 25 100% 4= 0

100/ 5= 20 100% 5= 0

100/ 6= 16 100% 6= 4

100/ 7= 14 100% 7= 2

100/ 8= 12 100% 8= 4

100/ 9= 11 100% 9= 1

100/10= 10 100%10= 0

6.2.5

Spojení řetězců

Připomeňme si, že operátorem + je kromě aritmetického součtu možné provést spojení dvou řetězců. Jak si řekneme později, jsou řetězce neměnitelné (immutable), takže výsledkem spojení dvou řetězců je řetězec nový:

// Základy programovacího jazyka Go

//

// - spojení řetězců operátorem +

package main

import "fmt"

func main() {

// dvojice proměnných typu string

var x string = "Hello "

var y string = "world!"

// výpis původních hodnot proměnných

fmt.Printf("x = '%s'\n", x)

fmt.Printf("y = '%s'\n", y)

fmt.Println()

// spojení řetězců operátorem +

fmt.Printf("x+y = %s\n", x+y)

}

Výsledek vypsaný tímto příkladem:

x = 'Hello '

y = 'world!'

x+y = Hello world!

6.2.6

Relační operátory

Nabídka relačních operátorů vprogramovacím jazyce Go by nás neměla ničím nepřekvapit: kdispozici je totiž všech šest základních operátorů pro test na rovnost, nerovnost, větší než, menší než akombinací větší nebo rovno amenší nebo rovno. Těchto šest operátorů je možné použít pro všechny celočíselné datové typy ipro datové typy splovoucí řádovou čárkou. Kromě toho ovšem můžeme porovnávat iřetězce. Navíc je možné test na rovnost anerovnost použít ipro porovnání struktur atd.– stímto velmi užitečným konceptem se seznámíme vnavazujících kapitolách.

Nejprve si ukažme porovnání dvou proměnných typu celé číslo:

// Základy programovacího jazyka Go

//

// - základní relační operátory sproměnnými

// typu int

package main

import "fmt"

func main() {

// dvojice proměnných typu int

var x int = 1

var y int = 2

// výpis původních hodnot proměnných

fmt.Printf("x = %d\n", x)

fmt.Printf("y = %d\n", y)

fmt.Println()

// výpis výsledků relačních operací sproměnnými

fmt.Printf("x == y: %t\n", x == y)

fmt.Printf("x != y: %t\n", x != y)

fmt.Printf("x > y: %t\n", x > y)

fmt.Printf("x >= y: %t\n", x >= y)

fmt.Printf("x < y: %t\n", x < y)

fmt.Printf("x <= y: %t\n", x <= y)

}

Výsledky:

x = 1

y = 2

x == y: false

x != y: true

x > y: false

x >= y: false

x < y: true

x <= y: true

Naprosto stejným způsobem lze porovnávat idvě hodnoty splovoucí řádovou čárkou:

// Základy programovacího jazyka Go

//

// - základní relační operátory sproměnnými

// typu float32

package main

import "fmt"

func main() {

// dvojice proměnných typu float32

var x float32 = 1.1

var y float32 = 2.2

// výpis původních hodnot proměnných

fmt.Printf("x = %f\n", x)

fmt.Printf("y = %f\n", y)

fmt.Println()

// výpis výsledků relačních operací sproměnnými

fmt.Printf("x == y: %t\n", x == y)

fmt.Printf("x != y: %t\n", x != y)

fmt.Printf("x > y: %t\n", x > y)

fmt.Printf("x >= y: %t\n", x >= y)

fmt.Printf("x < y: %t\n", x < y)

fmt.Printf("x <= y: %t\n", x <= y)

}

Nyní budou výsledky vypadat stejně, jako vpředchozím příkladu:

x = 1.100000

y = 2.200000

x == y: false

x != y: true

x > y: false

x >= y: false

x < y: true

x <= y: true

Ukomplexních čísel, tj. udatových typů complex64 acomplex128 je ovšem situace odlišná, protože tyto hodnoty můžeme porovnávat jen na rovnost anerovnost (tvůrci programovacího jazyka Go se tak vyhnuli problémům, jak vlastně definovat relaci mezi dvojicí komplexních čísel):

// Základy programovacího jazyka Go

//

// - základní relační operátory sproměnnými

// typu complex64

package main

import "fmt"

func main() {

// dvojice proměnných typu complex64

var x complex64 = 1 + 0i

var y complex64 = 1 + 1i

// výpis původních hodnot proměnných

fmt.Printf("x = %v\n", x)

fmt.Printf("y = %v\n", y)

fmt.Println()

// výpis výsledků relačních operací sproměnnými

fmt.Printf("x == y: %t\n", x == y)

fmt.Printf("x != y: %t\n", x != y)

}

Výsledky:

x = (1+0i)

y = (1+1i)

x == y: false

x != y: true

Zajímavé je, že hodnoty typu boolean, tj. hodnoty true afalse, je možné vprogramovacím jazyce Go porovnávat pouze na rovnost anerovnost. Zbylé čtyři relační operátory (tj. například „menší než“) nelze vtomto případě použít, tj. vjazyce Go není možné rozhodnout, zda je hodnota true větší či menší než false. Naproti tomu například vPascalu nebo Pythonu platí, že false < true:

// Základy programovacího jazyka Go

//

// - základní relační operátory sproměnnými

// typu boolean

package main

import "fmt"

func main() {

// dvojice proměnných typu bool(ean)

var x bool = true

var y bool = false

// výpis původních hodnot proměnných

fmt.Printf("x = %t\n", x)

fmt.Printf("y = %t\n", y)

fmt.Println()

// výpis výsledků relačních operací sproměnnými

fmt.Printf("x == y: %t\n", x == y)

fmt.Printf("x != y: %t\n", x != y)

fmt.Printf("x > y: %t\n", x > y)

fmt.Printf("x >= y: %t\n", x >= y)

fmt.Printf("x < y: %t\n", x < y)

fmt.Printf("x <= y: %t\n", x <= y)

}

Tento příklad nebude možné přeložit:

./02_09_relational_operators_boolean.go:16:28: invalid operation: x > y (operator > not defined on bool)

./02_09_relational_operators_boolean.go:17:29: invalid operation: x >= y (operator >= not defined on bool)

./02_09_relational_operators_boolean.go:18:28: invalid operation: x < y (operator < not defined on bool)

./02_09_relational_operators_boolean.go:19:29: invalid operation: x <= y (operator <= not defined on bool)

Nakonec si ukažme lexikografické porovnání dvou řetězců, což je vGo povolená operace (nikoli však například vjazyku C):

// Základy programovacího jazyka Go

//

// - základní relační operátory sproměnnými

// typu řetězec

package main

import "fmt"

func main() {

// dvojice proměnných typu string

var x string = "foo"

var y string = "bar"

// výpis původních hodnot proměnných

fmt.Printf("x = '%s'\n", x)

fmt.Printf("y = '%s'\n", y)

fmt.Println()

// výpis výsledků relačních operací sproměnnými

fmt.Printf("x == y: %t\n", x == y)

fmt.Printf("x != y: %t\n", x != y)

fmt.Printf("x > y: %t\n", x > y)

fmt.Printf("x >= y: %t\n", x >= y)

fmt.Printf("x < y: %t\n", x < y)

fmt.Printf("x <= y: %t\n", x <= y)

}

Výsledky vypsané tímto ukázkovým příkladem budou vypadat následovně:

x = 'foo'

y = 'bar'

x == y: false

x != y: true

x > y: true

x >= y: true

x < y: false

x <= y: false

6.2.7

Logické operátory

Vprogramovacím jazyku Go dále můžeme použít trojici operátorů, které jsou aplikovatelné na logické (pravdivostní) hodnoty true afalse (tedy hodnot typu bool). Jedná se ologickou negaci, logický součin alogický součet. Tyto tři operátory se zapisují následujícím způsobem:

Operátor

Význam operátoru

!

negace pravdivostní hodnoty

&&

logický součin

||

logický součet

Základní funkci těchto tří operátorů si můžeme velmi snadno otestovat:

// Základy programovacího jazyka Go

//

// - logické operátory (pro hodnoty typu bool)

package main

import "fmt"

func main() {

// dvojice proměnných typu bool(ean)

var x bool = true

var y bool = false

// výpis původních hodnot

fmt.Printf("x = %t\n", x)

fmt.Printf("y = %t\n", y)

fmt.Println()

// výpis výsledků operací

fmt.Printf("not x = %t\n", !x)

fmt.Printf("not y = %t\n", !y)

fmt.Printf("x && y = %t\n", x && y)

fmt.Printf("x || y = %t\n", x || y)

}

Svýsledky:

x = true

y = false

not x = false

not y = true

x && y = false

x || y = true

Vjazyce Go není možné použít varianty & ani | snezkráceným vyhodnocením výrazu. Operátory && a|| vždy používají zkrácené vyhodnocování (short circuit), což konkrétně znamená, že pokud je po vyhodnocení prvního operandu zřejmé, jaký bude výsledek operace, druhý operand se nevyhodnocuje. To má význam vpřípadě, že se při vyhodnocování volají funkce popř. pokud se například dělí nulou atd.:

// Základy programovacího jazyka Go

//

// - zkrácené vyhodnocení operátorů && a||

package main

import "fmt"

func f1() bool {

println("f1")

return true

}

func f2() bool {

println("f2")

return false

}

func f3() bool {

println("f2")

return false

}

func main() {

// zkrácené vyhodnocení operátorů && a|| má vliv na to,

// které funkce f1() af2() se budou volat

fmt.Printf("short circuit &&: %v\n", f1() && f2())

fmt.Printf("short circuit ||: %v\n", f1() || f2())

fmt.Printf("short circuit &&: %v\n", f2() && f3())

fmt.Printf("short circuit ||: %v\n", f2() || f3())

}

Při pohledu na výsledky vypsané tímto příkladem se skutečně můžeme přesvědčit, že se druhá funkce ve výrazu vněkterých případech vůbec nevolá:

f1

f2

short circuit &&: false

f1

short circuit ||: true

f2

short circuit &&: false

f2

f2

short circuit ||: false

6.2.8

Bitové operátory

Nyní se budeme zabývat bitovými operacemi, které jsou vprogramovacím jazyce Go realizovány svyužitím pětice operátorů. Tyto operátory jsou vypsány vnásledující tabulce:

Operátor

Význam operátoru

^

negace bit po bitu (podobně jako operátor ~ vC)

&

logický součin prováděný bit po bitu

|

logický součet prováděný bit po bitu

^

logická nonekvivalence prováděná bit po bitu

&^

maskování bitů vybraných zadanou maskou (operace AND NOT)

První operátor je unární, tj. aplikuje se pouze na jediný operand, který je zapsán za operátor, podobně jako udalších unárních operátorů (operace ++ a– jsou výjimkou, ale vtomto případě se nejedná oplnohodnotné operátory). Tento operátor slouží pro negaci všech bitů vceločíselné hodnotě abyl již popsán vpodkapitole se všemi unárními operátory. Tento operátor je možné aplikovat jak na hodnoty bez znaménka (unsigned), tak ina hodnoty se znaménkem (signed).

Poznámka: vprogramovacích jazycích odvozených od céčka se tento operátor většinou nezapisuje znakem ^, ale svyužitím znaku ~, což může být matoucí.

Další trojice operátorů zapisovaných znaky &, | a^ patří mezi binární operátory, které jsou aplikovány na dvojici operandů. Opět se může jednat jak ohodnoty se znaménkem, tak iohodnoty bez znaménka. Význam těchto operátorů odpovídá podobně zapisovaným operátorům vC, C++ či Javě. Zajímavější ave vyšších programovacích jazycích pravděpodobně iunikátní je však poslední zbitových operátorů, který se zapisuje dvojicí znaků &^. Tento operátor provádí logický součin bit po bitu, ovšem všechny bity druhého operandu jsou nejdříve znegovány. Operátor &^ se tedy používá pro takzvané maskování, kdy například budeme potřebovat vynulovat bit 1 pomocí operace x &^ 1 atd.

Všechny bitové operátory jsou použity vtomto demonstračním příkladu:

// Základy programovacího jazyka Go

//

// - bitové operátory pro osmibitové hodnoty

package main

import "fmt"

func main() {

// dvojice proměnných typu byte

// (osmibitové celé číslo bez znaménka)

var x byte = 42

var y byte = 100

// výpis původních hodnot

fmt.Printf("x = %08b\n", x)

fmt.Printf("y = %08b\n", y)

// výpis výsledků bitových operací

fmt.Printf("neg x = %08b\n", ^x)

fmt.Printf("neg y = %08b\n", ^y)

fmt.Printf("x && y = %08b\n", x&y)

fmt.Printf("x || y = %08b\n", x|y)

fmt.Printf("x ^ y = %08b\n", x^y)

fmt.Printf("x &^ y = %08b\n", x&^y)

}

Výsledky:

x = 00101010

y = 01100100

neg x = 11010101

neg y = 10011011

x && y = 00100000

x || y = 01101110

x ^ y = 01001110

x &^ y = 00001010

Využít lze i32bitové operace:

// Základy programovacího jazyka Go

//

// - bitové operátory pro 32bitové hodnoty

package main

import "fmt"

func main() {

// dvojice proměnných typu uint32

// (32bitové celé číslo bez znaménka)

var x uint32 = 42

var y uint32 = 100

// výpis původních hodnot

fmt.Printf("x = %032b\n", x)

fmt.Printf("y = %032b\n", y)

// výpis výsledků bitových operací

fmt.Printf("neg x = %032b\n", ^x)

fmt.Printf("neg y = %032b\n", ^y)

fmt.Printf("x && y = %032b\n", x&y)

fmt.Printf("x || y = %032b\n", x|y)

fmt.Printf("x ^ y = %032b\n", x^y)

fmt.Printf("x &^ y = %032b\n", x&^y)

}

Opět se podívejme na výsledky:

x = 00000000000000000000000000101010

y = 00000000000000000000000001100100

neg x = 11111111111111111111111111010101

neg y = 11111111111111111111111110011011

x && y = 00000000000000000000000000100000

x || y = 00000000000000000000000001101110

x ^ y = 00000000000000000000000001001110

x &^ y = 00000000000000000000000000001010

Poznámka: povšimněte si, že pokud se bitové operátory použijí ve své základní formě (tj. bez přiřazení), zapisují se „nalepené“ na operandy– nepoužívají se zde oddělovací mezery (viz příkaz go fmt).

6.2.9

Bitové posuny

Dále si ukažme použití bitových posunů (bit shifts). Utěchto operátorů je nutné dodržet jedno pravidlo– samotný posun (tedy pravý operand) musí být reprezentován kladným číslem (konstantou) popř. proměnnou typu celé kladné číslo. Nejprve si ukažme, jak proběhne bitový posun doleva adoprava uhodnoty typu byte (tj. budeme posunovat osmibitové celé číslo bez znaménka):

// Základy programovacího jazyka Go

//

// - bitové posuny pro hodnoty typu byte

package main

import "fmt"

func main() {

// celočíselný typ bez znaménka

var x byte = 42

// zobrazení původní hodnoty x

fmt.Printf("x = %08b\n", x)

// zobrazení hodnoty x po jejím posunu doprava adoleva

// vždy ojeden bit

fmt.Printf("x>>1 = %08b\n", x>>1)

fmt.Printf("x<<1 = %08b\n", x<<1)

}

Pro lepší názornost je jak vstupní hodnota, tak ivýsledek posunu doprava resp.doleva, zobrazen ve dvojkové soustavě:

x = 00101010

x>>1 = 00010101

x<<1 = 01010100

Uceločíselných hodnot se znaménkem se provedou aritmetické posuny, tj. takové operace, které zachovají znaménko původní (posouvané) hodnoty:

// Základy programovacího jazyka Go

//

// - bitové posuny pro hodnoty typu int

package main

import "fmt"

func main() {

// celočíselný typ se znaménkem

var x int = -42

// zobrazení původní hodnoty x

fmt.Printf("x = %08b\n", x)

// zobrazení hodnoty x po jejím posunu doprava adoleva

// vždy ojeden bit

fmt.Printf("x>>1 = %08b\n", x>>1)

fmt.Printf("x<<1 = %08b\n", x<<1)

}

x = -0101010

x>>1 = -0010101

x<<1 = -1010100

Vpřípadě, že je posun (pravý operand) specifikován proměnnou, musí se jednat oceločíselnou proměnnou bez znaménka, tj. unsigned int. Například takto lze realizovat násobení mocninou dvou nebo naopak dělení mocninou dvou:

// Základy programovacího jazyka Go

//

// - bitové posuny ohodnotu proměnné (bez znaménka)

package main

import "fmt"

func main() {

// původní hodnota x

var x int = 1

// postupné násobení hodnoty x mocninami dvojky

// (čítač ije použit na pravé straně operátoru posunu)

for i:= uint(0); i<= 10; i++ {

fmt.Printf("%d << %2d == %4d\n", x, i, x<<i)

}

fmt.Println()

// původní hodnota x

x = 10000000

// postupné dělení hodnoty x mocninami dvojky

// (čítač ije použit na pravé straně operátoru posunu)

for i:= uint(0); i<= 10; i++ {

fmt.Printf("%d >> %2d == %4d\n", x, i, x>>i)

}

}

Výsledky získané po překladu aspuštění tohoto ukázkového příkladu:

1 << 0 == 1

1 << 1 == 2

1 << 2 == 4

1 << 3 == 8

1 << 4 == 16

1 << 5 == 32

1 << 6 == 64

1 << 7 == 128

1 << 8 == 256

1 << 9 == 512

1 << 10 == 1024

10000000 >> 0 == 10000000

10000000 >> 1 == 5000000

10000000 >> 2 == 2500000

10000000 >> 3 == 1250000

10000000 >> 4 == 625000

10000000 >> 5 == 312500

10000000 >> 6 == 156250

10000000 >> 7 == 78125

10000000 >> 8 == 39062

10000000 >> 9 == 19531

10000000 >> 10 == 9765

6.2.10

Provedení operace společně spřiřazením

Vtabulce se všemi operátory, která byla zobrazena na začátku této kapitoly, se nachází mj.ioperátory, které provádí vybranou operaci současně spřiřazením výsledku do specifikované proměnné popř. do vybraného prvku pole nebo struktury. Jedná se onásledujících jedenáct operátorů:

+= &=

-= |=

*= ^=

/= <<=

%= >>=

&^=

Tyto operátory používají následující zápis:

proměnná ●= výraz

Ten se vlastně interně rozepíše na:

proměnná = proměnná ● výraz

Přičemž namísto znaku ● je nutné doplnit jeden zpodporovaných operátorů: +, -, *, /, %, &, |, ^, >>, << nebo &^.

Vyzkoušejme si nejdříve zápis aritmetických operátorů spřiřazením:

// Základy programovacího jazyka Go

//

// - základní aritmetické operátory kombinované

// spřiřazením

package main

import "fmt"

func main() {

// původní hodnota x

var x int = 1

// zobrazení původní hodnoty uložené vx

fmt.Printf("x = %d\n", x)

// modifikace x aritmetickou operací +

x += 10

fmt.Printf("x = %d\n", x)

// modifikace x aritmetickou operací *

x *= 2

fmt.Printf("x = %d\n", x)

// modifikace x aritmetickou operací /

x /= 3

fmt.Printf("x = %d\n", x)

// modifikace x aritmetickou operací %

x %= 2

fmt.Printf("x = %d\n", x)

}

Výsledky:

x = 1

x = 11

x = 22

x = 7

x = 1

Vdalším příkladu je realizováno postupné násobení vstupní hodnoty 1 dvěma anásledně dělení jiné vstupní hodnoty 10000000 taktéž mocninami dvojky:

// Základy programovacího jazyka Go

//

// - operátory pro bitové posuny kombinované

// spřiřazením

package main

import "fmt"

func main() {

// původní hodnota x

var x int = 1

// postupné násobení hodnoty uložené vx dvojkou

for i:= uint(0); i<= 10; i++ {

fmt.Printf("%d << %2d == %4d\n", 1, i, x)

// výpočet další hodnoty vřadě

x <<= 1

}

fmt.Println()

// původní hodnota x

x = 10000000

// postupné dělení hodnoty uložené vx dvojkou

// (imusí být numerický typ bez znaménka)

for i:= uint(0); i<= 10; i++ {

fmt.Printf("%d >> %2d == %4d\n", 1, i, x)

// výpočet další hodnoty vřadě

x >>= 1

}

}

1 << 0 == 1

1 << 1 == 2

1 << 2 == 4

1 << 3 == 8

1 << 4 == 16

1 << 5 == 32

1 << 6 == 64

1 << 7 == 128

1 << 8 == 256

1 << 9 == 512

1 << 10 == 1024

1 >> 0 == 10000000

1 >> 1 == 5000000

1 >> 2 == 2500000

1 >> 3 == 1250000

1 >> 4 == 625000

1 >> 5 == 312500

1 >> 6 == 156250

1 >> 7 == 78125

1 >> 8 == 39062

1 >> 9 == 19531

1 >> 10 == 9765

Vynulování vybraných bitů resp.nastavení vybraných bitů na jedničku:

// Základy programovacího jazyka Go

//

// - operátory pro bitové operace kombinované

// spřiřazením (použito uproměnné typu byte)

// - použití celočíselných konstant zapsaných

// ve dvojkové soustavě

package main

import "fmt"

func main() {

// původní hodnota x (osmibitové celé číslo bez znaménka)

var x byte = 0b11110000

fmt.Printf("original x: %08b\n", x)

// nastavení bitu číslo 0 na jedničku

x |= 0b00000001

fmt.Printf("set bit #0: %08b\n", x)

// vynulování bitu číslo 7

x &= 0b01111111

fmt.Printf("reset bit #7: %08b\n", x)

// maskování hodnoty operací AND NOT

// (maska je negována apoté se provede bitová operace AND)

x &^= 0b11110000

fmt.Printf("masked: %08b\n", x)

}

Průběh maskování:

original x: 11110000

set bit #0: 11110001

reset bit #7: 01110001

masked: 00000001

6.2.11

Zvýšení asnížení hodnoty ojedničku

Programovací jazyk Go umožňuje jednoduché zvýšení či snížení nějaké číselné hodnoty (celého čísla, čísla splovoucí řádovou čárkou, dokonce ičísla komplexního) uložené vproměnné ojedničku. Tyto operace jsou zapisovány znaky ++ a--, které musíme vždy zapsat až ZA operand. Na rozdíl od prakticky všech „céčkovských“ programovacích jazyků ovšem není povoleno použít tyto znaky PŘED operandem. Navíc– což je ještě více omezující podmínka– musí být tyto operace zapsány jako příkaz (statement), nikoli jako součást složitějšího výrazu (expression). Ztěchto důvodů se operace ++ a-- většinou ani neuvádí vseznamu operátorů, protože tvoří samostatnou syntaktickou kategorii.

Poznámka: tato omezení jsou velmi praktická, protože odstraňují některé nejasnosti vchápání významu (sémantiky) operací ++ a--.

Nejjednodušší příklad použití operací ++ a-- může vypadat následovně:

// Základy programovacího jazyka Go

//

// - operátory ++ a-- použité pro proměnné typu int

package main

import "fmt"

func main() {

// původní hodnota x

var x int = 1

// výpis původní hodnoty

fmt.Printf("x = %d\n", x)

// zvýšení hodnoty uložené vproměnné x ojedničku

x++

// výpis nové hodnoty

fmt.Printf("x = %d\n", x)

// snížení hodnoty uložené vproměnné x ojedničku

x--

// výpis nové hodnoty

fmt.Printf("x = %d\n", x)

}

Výsledky:

x = 1

x = 2

x = 1

Dtto pro hodnoty splovoucí řádovou čárkou:

// Základy programovacího jazyka Go

//

// - operátory ++ a-- použité pro proměnné typu float32

package main

import "fmt"

func main() {

// původní hodnota x

var x float32 = 3.14

// výpis původní hodnoty

fmt.Printf("x = %f\n", x)

// zvýšení hodnoty uložené vproměnné x ojedničku

x++

// výpis nové hodnoty

fmt.Printf("x = %f\n", x)

// snížení hodnoty uložené vproměnné x ojedničku

x--

// výpis nové hodnoty

fmt.Printf("x = %f\n", x)

}

Opět si ukažme výsledky:

x = 3.140000

x = 4.140000

x = 3.140000

Taktéž je možné tyto operátory použít společně skomplexními čísly. Vtomto případě se operace týkají reálné složky komplexních čísel, zatímco imaginární složka zůstane nezměněna:

//

// - operátory ++ a-- použité pro proměnné typu complex

package main

import "fmt"

func main() {

// původní hodnota z

var zcomplex64 = 1 + 2i

// výpis původní hodnoty

fmt.Printf("z= %v\n", z)

// zvýšení hodnoty uložené vproměnné x ojedničku

z++

// výpis nové hodnoty

fmt.Printf("z= %v\n", z)

// snížení hodnoty uložené vproměnné x ojedničku

z--

// výpis nové hodnoty

fmt.Printf("z= %v\n", z)

}

Ověřme si na výsledcích, jakým způsobem se tyto operátory chovají:

z= (1+2i)

z= (2+2i)

z= (1+2i)

Jak jsme si již řekli, jsou možnosti použití operací ++ a-- vprogramovacím jazyku Go omezenější, než je tomu vklasických jazycích odvozených od céčka. Vprvní řadě není dovoleno zapsat tyto operace před operandy. Na toto nekorektní použití nás upozorní překladač:

// Základy programovacího jazyka Go

//

// - nekorektní způsob použití operací ++ a--

package main

++x

}

}