A kifejezések egy elvégzendő számítást határoznak meg. A C++-hoz hasonlóan a Go-ban is nagy szerep jut a kifejezéseknek. A C++-szal ellentétben azonban itt nem követték a "minden legyen kifejezés" elvet, néhány olyan megszorítással éltek, mely a nyelvet biztonságosabbá teszi.
Az összetett literálok építenek értékeket rekordoknak, tömböknek, szeleteknek, és mapeknek. Minden egyes kiértékeléskor egy teljesen új értéket hoznak létre.
Például vegyük az alábbi egyszerű pont típust:
Ennek többféle módon adhatunk értéket: az értékeke megfelelő sorrendű felsorolásával, és kulcs-érték párokkal is.
Tömböket és szeleteket is hasonló módon inicializálhatunk. Kulcsos megadásnál a kulcsoknak mindenképpen számoknak kell lenniük. Itt már lehet keverni a kulcsos és a kulcs nélküli megadást, a kulcs nélkül megadott elemek mindig az előzőnél eggyel nagyobb indexűek lesznek. Ha egy tömbnél nem akarjuk megadni az elemszámot, akkor ...-tal helyettesíthetjük. Például az alábbi három megadás ekvivalens:
Ha a méret helyére nem írunk semmit, szeletet kapunk:
Mapet a tömb indexes változatához hasonló formában készíthetünk, de itt feltétlenül ki kell írnunk minden kulcsértéket:
Ha egy összetett literálon belül további összetett literálok vannak, és ezek típusa megegyezik a külső literál elemtípusával, a belső literálok típusát nem kell kiírni. Például a következő két kifejezés ugyan azt a két dimenziós szeletet adja.
A func szóval nem csak a fájl fő részében, valamint a típusdefinícióknál találkozhatunk, hanem kifejezésként is használható. Ekkor segítségével egy névtelen függvényt készíthetünk.
Ezek a függvénykifejezések zárványok (closure): hozzáférhetnek a bezáró függvény változóihoz, azok megosztásra kerülnek közöttük. Az ily módon több helyről elérhető változók addig élnek, amíg valahonnan elérhetők.
Alapkifejezésekből építhetünk operátorokkal bonyolultabb kifejezéseket. Példák alapkifejezésekre:
A szelektort a .
jelzi, és egy struct egy elemét választja ki, vagy egy (a változó típusán értelmezett) metódust hív meg. A pointerek egy szintet automatikusan feloldódnak a szelektor kifejezés hatására.
Tömbökből, szeletekből, stringekből és map-ekből is indexeléssel választhatjuk ki az i. elemét.
a
T típusú elemekből álló tömb/szelet, akkor
0
és len(a)-1
között kell lennie, különben futásidejű hibát kapunk.a
string, akkor
byte
típusú, a string i. bájtja0
és len(a)-1
között kell lennie, különben futásidejű hibát kapunka[i]
-t nem módosíthatjuk (pl. nem adhatunk neki értéket).a
map típusú, K típusú kulccsal és E típusú értékkel, akkor
nil
értékű vagy nem tartalmaz i-hez rendelt elemet, akkor az eredmény E típus alapértékev, ok = a[x]
, ekkor v
a hozzárendelt elem (vagy alapérték), ok
pedig egy logikai érték, amely azt jelzi, hogy volt-e hozzárendelt elem.A tömbök és a szeletek is tudják a határaikat, és a kiválasztás előtt mindig történik határellenőrzés.
Szeleteket képezhetünk tömbökből, stringekből és más szeletekből. Ezt az alábbi formában tehetjük meg:
A fenti kifejezés a
tömbből/szeletből/stringből választja ki a lo
-tól hi
indexig található elemeket. Az eredményül kapott szelet indexelése 0-tól kezdődik, és hi - lo
hosszan tart. Azaz az első kiválasztott elem a lo
pozíción lévő, az utolsó pedig a hi - 1
pozíción lévő.
A szeletelés egyik, vagy akár mindkét paramétere elhagyható, ekkor ezek szélsőértékekként értelmeződnek: azaz ha lo
-t hagyjuk el, az alsó határ 0 lesz, míg ha hi
-t hagyjuk el, a felső határ len(a)
-val fog megegyezni.
Stringek szeletelésekor stringet kapunk (tehát a kapott érték is immutable). Ha tömböt vagy szeletet szeletelünk, szeletet kapunk, mely az eredeti tömbbel közös memórián osztozik, a szelet elemeinek változtatása az eredeti tömb elemeinek változását is magával vonja és viszont.
k <= cap(t)
cap(t[i:j:k]) = k - i
k <= j - k
Lehetőségünk van arra, hogy a program egy adott pontján ellenőrizzük, hogy egy változó dinamikus típusa megfelelő-e számunkra. Ha a
egy interface típusú érték, akkor:
kifejezés ellenőrzi, hogy a
típusa jó-e. Ha T nem egy interfész, akkor pontos egyezést vizsgál T-vel, ha T egy interfész, akkor azt vizsgálja, hogy a
dinamikus típusa megvalósítja-e azt az interfészt.
Ha a
típusa valóban megfelel a fentieknek, akkor a kifejezés eredménye a
, de immáron T típussal. Egyébként (pl. akkor is, ha a
értéke nil
) futásidejű hibát kapunk. Azaz habár a
típusa nem ismert fordítási időben, de egy helyes programtól elvárjuk, hogy T típusú legyen.
Egy gyengébb megkötést enged az alábbi forma:
Ekkor ok
egy logikai változó lesz, mely tartalmazza, hogy az értékadás sikeres volt-e, v
-nek értékül tudtuk-e adni x
-et. Ha ez mégsem teljesül, akkor v
értéke T alapértéke lesz.
Az operátorok az operandusaikat kifejezésekké kombinálják. Az operandusok (az összehasonlító operátorok esetének és a biteltolásnak a kivételével) azonos típusúaknak kell lenniük. Ha egy típusos kifejezésre és egy nemtípusos konstansra alkalmazunk operátort, akkor a konstans (ha ez lehetséges), a másik operandus típusára konvertálódik.
Az operátorok precedenciája az alábbi:
Az azonos szinten levő operátorok balról jobbra értékelődnek ki. A ++
és a --
nem operátorok, hanem utasítások, így gyakorlatilag ezek kötnek a leggyengébben. Szintén utasítás a csatornára való adatküldés (->
). A kiértékelés sorrendje zárójelezéssel a szokásos módon befolyásolható.
Az aritmetikai operátorok numerikus típusokon értelmezettek, eredményük az első operandus típusával megegyező típusú. A négy alapművelet (+, -, *, /
minden numerikus típuson értelmezett; a +
operátor stringek konkatenációjára is alkalmas. A többi aritmetikai operátor csak egészekre használható.
Egészek között az osztás (/
) eredménye egész, a nulla felé csonkolva (kivételt képez, ha az osztandó x
a típusában ábrázolható legkisebb negatív szám: ekkor x / -1 == x
, ill. x % -1 == 0
). Nullával való osztás futásidejű hibát okoz.
Az osztásnak a maradékképzés (%
) operátorral az alábbi kapcsolata áll fenn:
A biteltolás operátorok bal operandusuk bitjeit tolják el a jobb operandus által megadott mértékben. Ha a bal operandus előjeles egész, akkor aritmetikai eltolást végeznek (tehát az előjelet nem befolyásolják), míg előjel nélküli bal operandus esetén logikai eltolást hajtanak végre.
Az eltolás mértékének felső korlátja nincs: n
-nel való eltolás azonos n
-szeri 1-gyel való eltolással az adott irányban.
Előjel nélküli egészekre a +, -, *, <<
operátorok modulo 2^n
értelmezettek (n
az ábrázoláshoz használt bitek száma), azaz túl- vagy alulcsordulás esetén az értékek "körbejárnak" (wrap around).
Előjeles egészekre ugyanezen operátorok esetén a túlcsordulás megengedett, hiba nem keletkezik és az eredménynek determinisztikusnak kell lennie. További megkötés a fordítók számára, hogy nem optimalizálhatnak a túlcsordulás lehetőségét kizárva, pl. nem tehetik fel, hogy x < x + 1
mindig igaz lesz.
Az összehasonlító operátorok két értéket hasonlítanak össze, eredményük logikai érték. Az összehasonlítás során az első operandus értékül adható kell legyen a második operandusnak, vagy fordítva.
Az ==, !=
operátorok operandusainak összehasonlíthatónak kell lenniük. A összehasonlítható típusok:
nil
nil
értékkel (pointerek, csatornák és interface-ek ugyancsak összehasonlíthatók a nil
-lel).
A rendezés operátorok operandusaitól a nyelv megköveteli a rendezhetőséget. A rendezhető típusok:
A logikai operátorok (&&, ||, !
) logikai értékeken értelmezettek a szokásos módon. A jobb oldali operandus csak szükség esetén (lustán) értékelődik ki.
Két pointer-operátor használható a nyelvben:
Címet kérni címezhető objektumtól (változó, pointer dereferencia, szelet-indexelés kifejezés, rekord mezője, címezhető tömb indexelése), illetve összetett literáltól lehet. Utóbbi esetben a literál által meghatározott új objektum létrejön és a kifejezés értéke ezen objektumra mutató pointer lesz.
nil
pointer feloldásának kísérlete futásidejű hibát okoz.
A fogadó operátor unáris, operandusa egy fogadásra (is) alkalmas csatorna, eredménye pedig a csatornáról fogadott érték. A kifejezés blokkol, amíg nincs a csatornáról elérhető érték (így nil
csatornáról való fogadás örökre blokkol). Példák:
A fogadó kifejezés speciális formája a következő: x, ok := <-ch
. Ebben a formában az ok
változó logikai típusú és azt adja meg, hogy a fogadott x
érték a csatornáról érkezett-e (ekkor ok
igaz), vagy alapérték (ha a csatorna zárva van és üres).
Függvényhívás a szokásos formában történik, a függvényt eredményező kifejezés (pl. függvénynév vagy literál) után zárójelben felsoroljuk a paramétereket:
Az f
függvény aktuális paramétereinek értékül adhatónak kell lennie a formális paramétereinek. A függvény paraméterei a hívás előtt, pontosan egyszer értékelődnek ki. A visszatérési érték(ek) típusa a függvény visszatérési típusának (típusainak) megfelelő.
nil
értékű függvénykifejezés hívása futásidejű hibát eredményez.
Ha egy g
függvény visszatérési értékei (bár többen vannak) számban és típusban megegyeznek egy f
függvény paramétereivel, akkor engedélyezett az f ( g ( /* paraméterek */ ) )
egyszerűsített függvényhívás.
...
paraméterek
Speciális eset a ...
paraméter használata: ha egy függvény utolsó paramétere params ...T
alakú, azt a következő két formában használhatjuk:
Ha T egy olyan típus, amely rendelkezik M metódussal
ezt a metódust hívja meg a megfelelő paraméterekkel. A paraméterekre és a visszatérési értékre a függvényekkel azonos szabályok vonatkoznak.
Metódusok deklarálásáról lásd a Függvény- és metódusdeklarációk szakaszt.
func (t *T) F(a int) int
definíciójú függvény a következő függvényszignatúrának felel meg: func(t *T, a int) int
és akár meg is hívható ilyen formában, a következő két hívás ekvivalens: t.F(1)
és T.F(t,1)
Az 1.1-es verzióval vezették be a nyelv készítői a metódus értéket, ezzel egy típus adott értékéhez lerögzített függvényt kaphatunk.
A müködését ez a kódrészlet szemlélteti:
DistanceFromOrigo
változó értéke egy függvény, ami a Distance metódus egy specializált példánya, mely mindig az origótól való távolságot adja meg, szignatúrája sem tartalmazza már paraméterként a Point-ot, amin meghívtuk a metódust: func(p2 Point) float64
.
A típuskonverziók Pascal-szerű formában történnek:
Ha a típus operátorral kezdődik, akkor zárójelbe kell tenni, pl.
A T(x)
konverzió az alábbi esetekben sikeres:
A számok közötti és a szám-string konverzióban felléphet futásidejű költség, illetve pontosságvesztés; a többi konverzió csak az adott változó típusát változtatja meg, reprezentációját nem.
Pointer és egész számok közötti konverzió nem engedélyezett.
A numerikus típusok közötti konverzióra az alábbi szabályok vonatkoznak:
Egész érték stringgé konvertálása esetén a string egyetlen karaktere az egész számnak megfelelő Unicode kódpont lesz - ha nincs megfelelő kódpont, akkor a \uFFFD
érték.
Ha bájt-szeletet ([]byte) konvertálunk stringgé, a szelet bájtjainak megfelelő karakterek rendre a string bájtjai lesznek. nil
szelet konverziója üres stringet eredményez.
[]rune
típus konverziója során az egyes rune
értékek string-alakjának konkatenációja az eredmény. Ha a szelet nil
, az eredmény üres string.
Stringet []byte
típusra konvertálva az eredmény a string egyes bájtjainak sorozata. Ha a string üres, az eredmény []byte(nil)
.
Stringet []rune
-ra konvertálva a string unicode kódpontjainak szekvenciáját kapjuk eredményül (üres string esetén []rune(nil)
értéket).
A konstans kifejezésekben csak konstans részkifejezéseket és operátorokat használhatunk, és még fordítási időben kiértékelődnek. A kiértékelés mindig pontos, nincs információveszteség még akkor sem, ha a részeredmények tárolásához az ábrázolható típusoknál jelentősen nagyobb tárterületre van szükség (pl. const Huge = 1 << 1000).
A konstans kifejezésekben a típuskonverziókat szabadabban kezeli a nyelv.
Nemtípusos konstansok szabadon használhatók a konstans kifejezésekben ott, ahol a megfelelő típusos kifejezések használhatók. Bináris operátort alkalmazva két, különböző fajta nemtípusos konstansra, a művelet jellege és az eredmény típusa a két fajta közül az alábbi listában később elhelyezkedő fajtájú lesz: egész, karakter, lebegőpontos, komplex. Például ha egy nemtípusos egész konstans osztunk egy nemtípusos komplex konstanssal, akkor az eredmény egy (nemtípusos) komplex konstans lesz.
Konstans összehasonlító kifejezés mindig nemtípusos logikai konstanst eredményez.
Ha a konstans biteltolás kifejezés bal operandusa nemtípusos konstans, akkor az eredmény mindig (nemtípusos) egész konstans - különben a bal operandusnak megfelelő típusú (a biteltolás operátor bal operandusa mindig egész típusú).
Minden, a fentiektől eltérő esetben, mikor két nemtípusos konstanson alkalmazunk operátort, az eredmény azonos fajtájú nemtípusos konstans lesz.Implementációs megszorítás: egyes fordítók a nemtípusos lebegőpontos és komplex konstans kifejezések kiértékelésénél kerekíthetnek. Ez akár azzal a hatással is járhat, hogy egy (végtelen pontosság esetén) egész értékű lebegőpontos konstans nem lesz használható egészként. Pl. az alábbi egy
változó értéke nem biztos, hogy pontosan 1 lesz:
A típusos konstansok értékétől a nyelv megköveteli, hogy az pontosan ábrázolható legyen a megadott típussal. Így például az alábbiak fordítás hibát okoznak:
iota
konstans kifejezés
A speciális iota
kifejezés egész (nemtípusos) konstansok sorozatának előállítását könnyíti meg. Kezdeti értéke 0, és minden konstans változó deklarációjánál, amelyben felhasználjuk (ld. Konstans-deklaráció), eggyel nő. Ha a fordító egy új const
kulcsszót talál, az értéke újra 0 lesz.
Példa az iota
használatára:
A nyelv nem tartalmaz felsorolástípust, viszont az iota
szerepelhet konstans kifejezésben és ezek a kifejezések implicit ismételhetőek egy const
blokon belül, így elérhető hasonló viselkedés:
Egy értékadás vagy kifejezés elemei közt minden funkció- illetve metódushívás valamint kommunikáció balról jobbra értékelődik ki.
A fenti értékadásban ez a következő sorrendet jelenti: f()
, h()
, i()
, j()
, <-c
, g()
, végül k()
. Viszont a fentiekhez képest az x
-nek és az y
-nak, illetve az indexelések kiértékelésének sorrendjére nincs megkötés.