Go

A Programozás Wiki wikiből

A Go, a Google által fejlesztett, elsősorban rendszerprogramozásra szánt programozási nyelv. Fejlesztését 2007 szeptemberében kezdte el Robert Griesemer, Rob Pike és Ken Thompson. Rob Pike és Ken Thompson ismert unix fejlesztők, akik jelenleg a Google-nél dolgoznak. 2009-ben lett kész az első működő változat. a go gc fordítója futóképes a LInuxon, a MAC OS-en, a FreeBSD, OpenBSD és a Microsoft Windows rendszeren, az i386, amd64 és ARM CPU-kon. A nyelv célkitűzései:

  • Gyors és egyszerű fordítás
  • Több magon futóképes programok, jó kommunikációs készség
  • Szemétgyűjtés

Megpróbálja az interpretált nyelvek kényelmét nyújtani alacsony szinten, tehát jó teljesítménnyel.

Alapok[szerkesztés]

Hello World![szerkesztés]

1 package main
2 import ”fmt”
3 func main() {
4     fmt.Println(”Hello World!”)
5 }


Szerkezete[szerkesztés]

1: Minden forrásfájl, valamilyen csomagba tartozik. A belépési pont csomagja, mindig ”main”

2: import lista. Felsorolható külön import kulcsszavakkal:

import ”fmt”
import ”os”
..

vagy egyben:

import (
    ”fmt”
    ”os”
)

A névterek átnevezhetőek:

import format_io ”fmt”
...
format_io.Println(”Hello World!”)

3: Program belépési pontja, main() függvény.

4: formázott kiiratás


Változók deklarációja[szerkesztés]

var var_name1, var_name2, ... ,var_namen type pl:

var a,b,c int

kezdeti értékadással:

var a,b,c int = 0,1,2

gyors deklaráció: var_name1,var_nam2, ... ,var_namen := value1,value2, .,. ,valuen pl:

a,b,c := 0,1,2

":=" operátor előtt szereplő változók típusa, rendre megfelel a := operátor után található kifejezések típusával. Ez talán azt a látszatot keltheti, hogy a nyelv engedékenyen bánik a típusokkal, de valójában nem így van. Minden változónak jól meghatározott típusa van. (legfeljebb nem ismerjük azt. Lásd: interface{} típus)

Tömbök[szerkesztés]

A tömbök működése, hasonló a c++ std::vector-aihoz. Rendelkeznek kapacitással, és hosszal. Ha egy tömbnek hosszabb tömböt adunk értékül mint a kapacitása, új memória területet foglal, az értékadásnak megfelelő kapacitással.

var arr []int = []int{0,1,2,3}
arr := []int{0,1,2,3}
len(arr)    // arr hossza
cap(arr)    // arr kapacitása


Asszociatív tömbök(Map-ek)[szerkesztés]

A Go nyelvi szinten támogatja típusos map-ek használatát, ami hasonló a c++-s std::map-hez. Tulajdonságaik az egyszerű tömbökkel megegyezőek.

var m map[string]int = map[string]int{”nulla”:0,”egy”:1,”kettő”:2,”három”:3}
m := map[string]int{”nulla”:0,”egy”:1,”kettő”:2,”három”:3}


Feltételek[szerkesztés]

if cond {
    ...
} else if cond {
    ...
} else {
    ...
}

szekvencia a feltétel kiértékelése előtt:

if kif1; kif2; cond {
    ...
}

Csellegő else probléma kizárása miatt, az alábbi C-ben megszokott forma nem megendegett:

if cond
    ...


Ciklusok[szerkesztés]

A Go nyelv, minden ciklust a for kulcsszóval jelöl.

for {    // végtelen ciklus
}
for cond {    // amíg cond feltétel teljesül
}
for kif1; kif2; kif3 {    // megszokott init, cond, iterate hármas vezérlés
}
for i,v := range arr {    // arr tömb, vagy map index, és value értékeinek befutása
}


Switch[szerkesztés]

Két típusa van, kifejezéses, és típusos switch(expression switch, type switch). (típus switch lásd: interface{}) kifejezéses switch-re példák:

switch tag {
    case 1: ...
    case 2,3: ...
    default: ...
}
switch x := f(); {
    case x == 0: ...
    default: ...
}
switch {
    case x < y: ...
    case y < x: ...
    default: ...
}


Függvények[szerkesztés]

Függvényeket, egy forrásprogram bármely pontján definiálhatunk.

Más függvény törzsén kívül:

func függvénynév(változólista) visszatérési_értékek {
    függvénytörzs
}

Változólistában, hasonlóan kell megadni az változókat, mint a hagyományos deklarációkor, csak elhagyva a var kulcsszót. Különböző típusú változókat, az első típusmeghatározás után vesszővel, majd folytatva lehet megadni.

pl:

func f(a,b int, x,y float, str1 string)

Visszatérési értékeknél, (ha több van(lásd: elem listák), azt is zárójelben) nem kötelező változóneveket megadni, ha mégis megadunk, a függvénytörzsben már nem kell őket deklarálnunk.


Egy függvény törzsén belül:

func(változólista) visszatérési_értékek {
    függvénytörzs
}

Ilyenkor ez egy függvény érték(akár egy int érték). Ha a bezáró kapcsos zárójel után írjuk a paraméterlistát, akkor függvény hívás lesz belőle. (lásd: defer, és függvény típusok – függvény változók)


Defer[szerkesztés]

A defer olyan konstrukció, mely elhalasztja egy függvény hívását, az aktuális függvény return utasítása utánra.

Ennek számos jó felhasználási területe van. Pl. tegyük fel, hogy egy függvényben esetlegesen megnyitunk egy fájlt, (amit illik is lezárni) azonban a megnyitás után, számos ponton hiba ellenőrzéseket kívánunk beszúrni a függvénybe ilyen-olyan okokból. A fájl lezárása, megbonyolítja a hiba ellenőrzését is, hiszen a megvan-e nyitva?, még nem zártam-e le?, stb.. típusú kérdéseket, változókkal kell kezelni. Ennél egyszerűbb, ha a megnyitáskor adok egy olyan utasítást, hogy a függvény futásának végeztével, lezárja a fájlt.

pl:

file, err := os.Open(srcName, os.O_RDONLY, 0)
defer file.Close()

Természetesen, több függvény hívását is elhalaszthatjuk. Ilyenkor ezek LIFO(Last In Firts Out) sorrendben hívódnak meg.

Fontos megemlíteni, hogy a defer, csak a függvény hívását halasztja el. Argumentumait, még az adott futási ponton kiértékeli.

Érdekesség még, hogy defer-ekkel, a visszatérési értéket is befolyásolni lehet.

pl:

func increment(i int) (i int) {
    defer func() { i++ }()
    return i
}


Panic és Recover[szerkesztés]

Go nyelvben, nincs kivételkezelés. Helyette, egy valamivel egyszerűbb konstrukció, a Panic, és Recover páros valósítja meg az összetettebb hibák kezelését.

A panic, és a recover is szintaktikai szinten, egyszerű függvényeknek látszanak. Szignatúrájuk:

func panic(interface{})           // panic-t valamilyen változól meghívjuk
func recover() interface{}        // és recover ezt adja vissza

Ha a program futása közben a vezérlés egy panic()-t talál, az egy futásidejű hibát(run-time panic) jelent, amit egy recover() tud kezelni.

Ha a recover() olyankor hívódik meg, mikor nem keletkezett panic, nil-t ad vissza.

Gyakorlatban, a recover(), a defer konstrukcióval karöltve használatos.

pl:

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println(”Hiba lépett fel, amit most kezelek: ”,r)
                ...
        }
    }()
    fmt.Println(”Még minden rendben”)
    panic(”Proba panic”)
    fmt.Println(”Ez már nem jelenik meg a kimeneten”)
}

Program futásának eredménye:

Még minden rendben
Hiba lépett fel, amit most kezelek: Proba panic

A run-time panic-ek recover() híján, mindig a hívó felé terjednek. (akár a kivételek) Ha a programban sehol nem kerül kezelésre egy run-time panic, a program természetesen megszakad.

Mutatók[szerkesztés]

Mutatók deklarációja: valtozo_nev *típus pl. egy integer pointer:

int_p *int

egy int tömbre mutató pointer:

int_arr_p *[]int

A címképző operátor, C stílusú nyelvekben megszokott & jel. Használatára pár példa:

int_p *int = &int_variable
int_p2 := int_variable2


New[szerkesztés]

Legegyszerűbb módja dinamikus objektumok létrehozására.

var int_p *int = new(int)
int_p := new(int)

egy tömb esetén:

int_arr_p := new([]int)

ilyenkor int_arr_p egy üres tömbre mutat.

Szükség lehet ennél konkrétabb megadásokra is. Erre a Make képes


Make[szerkesztés]

Rendeltetése, és használata, majdnem azonos a new-al. A make azonban kicsit okosabb.

int_arr := make([]int,16,32)

egy 32 kapacitású 16 hosszú tömböt hoz létre.

int_arr := make([]int,16)

pedig egy 16 hosszú, és 16 kapacitásút.


Elem listák[szerkesztés]

Go nyelvben lehetőség van, a különféle algoritmusok leírásánál is használt lista szerű értékadásra. pl:

a,b := 0,1
swap1,swap2 = swap2,swap1

Függvények is adhatnak vissza több értéket. Ha valamelyikre nincs szükségünk, _ jelet teszünk a helyére. pl:

func f1() (a,b int)
x,_ := f1()

A nyelv standard függvénykönyvtára sok helyen használja ezt a lehetőséget eredmények, és hibakódok visszadására.


Változók élettartama[szerkesztés]

A Go beépített Szemétgyűjtővel rendelkezik, így a memória kezelés felszabadítás terhét, leveszi a programozó válláról. A GC nem tesz különbséget élettartam szempontjából a dinamikus, és statikus helyfoglalású változók között. pl:

func new_int() *int {
    var i int
    return &i
}

Legtöbb nyelvben, a függvény lefutása után, i változó felszabadul. Itt azonban a GC nem tesz ilyet.

Típusok[szerkesztés]

Beépített típusok[szerkesztés]

uint8        uint16        uint32        uint64
int8         int16         int32         int64
float32      float64
complex64    complex128
byte         bool          uint          int
float        complex       uintptr       string

Különösebb magyarázatra talán csak a complex szorul:

valós, és képzetes része is float.

var v complex

valós része real(v)

képzetes része imag(v)


Értelemszerűen az uint, int, float, complex uintptr típusok mérete implementáció függő.


Új típusok[szerkesztés]

Új típusok létrehozására a type kulcsszót használhatjuk. Típusok létrehozására 4 konstrukciót használhatunk:

  • altípus
  • struct
  • interface (lásd: interface)
  • függvény


pl1:

type my_big_int int64
my_big_int változókat, és értékeket egyszerűen konvertálhatunk oda-vissza int64 típusúra
a := my_bing_int(0)
b := int(a)
c := my_big_int(b)

Ez a konverzó, bármikor elvégezhető két változó vagy érték között ha tudjuk, hogy a kettő szerkezete megegyezik. (azaz látjuk deklaráció szintjén) Előfordulhat olyan eset, amikor mi magunk tudjuk, hogy a konverzió elvégezhető, de a fordító ennek ellentmond. Ez egy rosszul megtervezett struktúrára utalhat.

pl2:

type datum struct {
    ev,ho,nap int
}
mai_nap := datum{2009,8,17}

pl3:

type my_comparable interface {
    Comp(my_comparable) bool
    Eq(my_comparable) bool
}

(Ennek értelmezését lásd: interface)

pl4:

type int_op func(a,b int) int


Függvény típusok – függvény változók[szerkesztés]

Mint már láttuk, a legtöbb nyelvhez hasonlóan, Go is tartalmaz olyan típusmeghatározásokat, melyek függvényekre vonatkoznak. Azonban, a Go ennél többet is kínál. A függvényeket, könnyedén változóként kezelhetjük. Vegyük a következő függvénytípust:

type int_op func(a,b int) int

Ez két int-et váró, és egy int-t visszaadó függvény típusát írja le. Ezt többféleképpen használhatjuk fel. A legáltalánosabb használata, hogy más függvényeknek paraméterül adjuk, vagy visszatérési értékként visszakapjuk. De deklarálhatunk is függvény típusú változókat, ezeknek értékül adhatunk már létező függvényeket, vagy akár helyben is definiálhatunk. Nézzünk ezekre példákat: pl1: Egy függvény, ami 2 int tömböt, és egy fent meghatározott típusú függvényt vár, majd ezt a függvényt végrehajtja minden elemen a 2 tömbön, és egy 3. tömbbe helyezi az értéket, amit aztán visszaad.

pl1:

func pl1(t1,t2 []int, f int_op) []int {
    r := make([]int,len(t1))
    for i:=0; i<len(t1); i++ {
        r[i] = f(t1[i],t2[i])
    }
    return r
}

A függvény szignatúráját így is írhattuk volna:

func pl1(t1,t2 []int, f func(a,b int) int) []int

pl2:

func getpl1(t1,t2 []int, f int_op) func() []int {
    return func() []int {
        return pl1(t1,t2,f)
    }
}

Ez a függvény, előállít egy olyan függvényt, mely bedrótozottan hajtja végre pl1-t. Vegyük észre, ha nem egyszerű tömbökkel dolgoznánk itt, hanem tömbökre mutató pointerekkel, egészen bonyolult problémákat oszthatnánk fel nagyon egyszerű logikai egységekre. Látható, hogy a nyelv kifejező ereje valóban nagy.

pl3:

Tegyük fel, hogy szükségünk van egy nagyon egyszerű függvényre, egy másik függvényen belül. Felesleges lenne ezzel bonyolítani a teljes csomagot. Egyszerűen definiálhatunk függvényeket, más függvények törzsében, hasonlóan a pl2-höz.

func main() {
    sum := func(a,b int) int {
        return a+b
    }
}

pl4:

Ezeket felhasználva, egy egyszerű példa:

package main
import ”fmt”
func pl1 ...
func getpl1 ...
func main() {
    sum := func ...
    t1,t2 := []int{1,2,3,4},[]int{5,6,7,8}
    my_pl1 := getpl1(t1,t2,sum)
    for i,v := range mypl1() {
        fmt.Printf(”%d: %d\n”,i,v)
    }
}


Metódus kifejezések[szerkesztés]

Hasonlóan az objektum-orientált nyelvekhez, Go-ban is lehet függvényeket definiálni típusokhoz. Nézzük meg az alábbi típust:

type vector struct {
    x,y int
}

Ehhez érték, és pointer szerint lehet metódust definiálni.

func (p *vector) add(a vactor) {
    p.x += a.x
    p.y += a.y
}

Ilyen esetekben(ahol változik az érték), mindenképpen pointer-el kell metódust definiálni.

Interface[szerkesztés]

A Go nyelv, interface-eken keresztül valósítja meg a polimorfizmust. Egy interface meghatározza, hogy egy típusnak milyen metódusokra van szüksége ahhoz, hogy az általunk kijelölt környezetben dolgozni lehessen vele. Tegyük fel, hogy implementálni szeretnénk egy rendezési algoritmust, azonban nem csak egy típusra szeretnénk ezt konkretizálni, hanem tetszőleges számúra. Rendezési algoritmusokhoz többnyire két művelet szükséges: <, =. Legyenek ezek most Comp, és Eq. E két műveletre bevezetett interface:

type My_comparable interface {
    Comp(a My_comparable) bool
    Eq(a My_comparable) bool
}

A rendezési függvény szignatúrája pl:

func Quicsort(arr *[]My_comparable, l,r int)

A függvény törzsében My_comparable típusú elemekről, pontosan csak annyit tudunk a típusról, amennyi szükséges.

Interface{}[szerkesztés]

Létezik egy speciális interface, ez az interface{}. Ez semmilyen megkötést nem tesz az adott típusról. Ezt használjuk olyan helyeken, ahol bármilyen típus szóbajöhet. pl. tárolóknál. A nyelv standard függvénykönyvtára is ezt használja listákhoz, vektorokhoz, gyűrűkhöz, stb...

Ez idáig nagyon egyszerűen hangzik, de hogyan kaphatjuk vissza, egy típus konkrétumait? Ez sem bonyolultabb. Két módszer is létezik:

1. Type assertion

2. Type switch

Type assertion, egy egyszerű kifejezés, melynek két visszatérési értéke van. Egyik a várt típus értéke, másik egy bool érték, hogy lehetséges volt-e megszorítás.

pl:

v, egy interface{}
i,x := v.(int)

Type switch, egy switch konstrukció ahol, a case-k különböző típusúak.

pl:

switch v.(type) {
    case int: ...
    case byte: ...
    default: ...
}

Az ágakon belül, biztosan az adott ágnak megfelelő típusú az x, de itt még egy type assertion kifejezést kell alkalmazni, hogy használhassuk (kvázi „tudjuk, de még nem látjuk”)


Egy komplexebb példa típusozásra Go-ban, a kifejezések kiértékelése Go nyelven.


Csomagok[szerkesztés]

Minden forrásfájlnak a köv. kell kezdődnie:

package package_name

ami meghatározza, hogy mely csomagba soroljuk az elemeit.

Minden programnak, a main csomagban található a belépési pontja.


Láthatóság[szerkesztés]

A csomagok elemeinek (más csomagokból való) láthatósága, a következő nagyon egyszerű szabályon alapszik: Ha nagybetűvel kezdődik, publikus, ha kicsivel, privát.

pl:

package my
type My_type struct {    // publikus típus
    x,y int        // privát adattagok
    A,B float        // publikus adattagok
}
type my_private_type struct {    // privát típus
    A int                // hiába publikus adattag
}


Párhuzamosítást, szinkronizációt támogató nyelvi elemek[szerkesztés]

Go[szerkesztés]

Go kulcsszóval lehet párhuzamos un. Goroutine-t indítani.

pl:

go func1()

Ez önmagában, az os csomagban található függvények mellett, nem kiemelkedő nyelvi elem.

Channel típusok[szerkesztés]

A goroutine-k csatorna változókon keresztül kommunikálhatnak egymással. Ezek alapvetően sor adatszerkezet kialakítású objektumok, amit osztott memóriával valósítanak meg. Ez egy kényelmes, és főleg gyors módja a párhuzamos kommunikációnak.

Csatorna változókat jellemzően make-el hoszunk létre, mert ezzel kényelmesen megadhatjuk, hogy hány elemet legyen képes tárolni a csatorna. Ha egy csatorna betelik(túl sok elemet teszünk rá anélkül, hogy egyet is levennénk), a goroutine futása megáll, vár amíg fel nem szabadul a csatornában egy hely, majd ráhelyezi a kívánt elemet, és folytatja futását. Jelölések:

chan int // egy int típusú csatorna változót jelöl.
ch := make(chan int,16) // ch, egy 16 elemű int csatorna
ch <- 0 // 0-t ráhelyezzük a csatornára
<- ch // leveszünk egy értéket a csatornáról. Ennek a kifejezésnek az értéke, a csatornáról leválasztott elem értéke.
X = <- ch // x-nek értékül adjuk.

Használatuk:

func p1(in <-chan int, out chan<- int) {
    ...
}

p1 egy párhuzamos futtatásra szánt függvény, (Látszik ez onnan, hogy csatornaváltozókat használ. Ez valójában nem kikötés, inkább csak ez jellemző) in csatornaváltozója, kimondottan input csatornának szánt, out pedig outputra.

func main() {
    in := make(chan int,16)
    out := make(chan int,16)
    go p1(out, in) // ami a főfolyamatnak input, az az al-folyamatnak input, és fordítva
    ...
}


Chanel típusok, szinkronizációhoz[szerkesztés]

Egy hagyományos csatorna változónak van hossza.

  • Ha betelik és újabb elemet akarunk rátenni, várakoznunk kell, míg felszabadul egy hely.
  • Ha teljesen üres, és elemet akarunk levenni a csatornáról, várakoznunk kell, míg valaki tesz rá egyet.

Ezt végiggondolva, adódik a lehetőség, hogy goroutine-okat úgy szinkronizáljunk, hogy csatornaváltozókon végzett műveletekkel igazítjuk a szinkronizációs pontjaikat.

Lehetőség van olyan csatornaváltozók létrehozására, melyeknek a hossza 0. Az ilyen csatornák, kvázi közvetlen kapcsolódási pontok lehetnek goroutine-ok között.

pl:

func main() {
    s := make(chan int)
    go func(c chan int) {
        ...    // tevékenység
        c <- 0 // szinkronizáció
        ...    // tevékenység
    }(s)
    ...  // tevékenység
    <- s // szinkronizáció
    ...  // tevékenység
}

Select[szerkesztés]

Mindezeknek a használata nehézkes, és komplikált lenne, egy megfelelő vezérlő konstrukció nélkül. Ez a select. Ismerős lehet Ada programozóknak, valójában igen hasonlóan működik. Switch-hez hasonlóan case ágai vannak, és annak az ágnak adja át a vezérlést, ahol először értelmes tevékenységet talál. (minden értelmes, ami nem Wait())

pl:

select {
    case i1 := <-c1:    fmt.Println(”c1 csatornán megjelent érték: ”,i1)
    case i2 := <-c2:    fmt.Println(”c2 csatornán megjelent érték: ”,i2)
    default:        fmt.Println(”nincs bejövő adat”)
}

select, kimenő csatornára is várakozhat:

select {
    case c1 <- v1:    fmt.Println(”c1-re tettem: ”,v)
    ...
}

üres select:

select {    // a goroutine blokkolása örökre. Általában nincs sok értelme.
}

Ha egy program futása alatt minden goroutine leblokkol, a program goroutine panic-t dob.

Külső hivatkozások[szerkesztés]

Ez a jegyzet, nyilvánvalóan nem teljes, további hasznos információk találhatóak:

http://golang.org/ A Go nyelv hivatalos honlapja

http://golang.org/doc/go_spec.html Nyelv specifikációja

http://golang.org/doc/go_tutorial.html Tutorial

http://golang.org/doc/effective_go.html Még egy kis tutorial

http://golang.org/pkg/ Csomag specifikáció