Programski jezik Go

U izradi. Bazirano na Go verziji 1.12.4 i A Tour of Go

Prof. dr Igor Dejanović (igord at uns ac rs)

Kreirano 2019-05-14 Tue 20:42, pritisni ESC za mapu

Uvod

Osnovne osobine

  • Statički tipiziran, kompajliran programski jezik
  • Google: Robert Griesemer, Rob Pike, i Ken Thompson
  • Sličan C-u ali sa memory safety, garbage collection i direktnom podrškom za konkurentno programiranje (Communicating Sequential Processes (CSP))
  • Efikasnost, jednostavnost i čitkost
  • Akcenat na mrežnom i multi-core softveru
  • Razvoj započet 2007, javno dostupan od 2009

Instalacija i podešavanja

Instalacija

  • Koristiti instaler za OS
  • …ili raspakovati tarball/zip i podesiti PATH varijablu na go/bin folder
  • Na primer:

    wget https://dl.google.com/go/go$VERSION.$OS-$ARCH.tar.gz
    mkdir -p ~/install/go
    tar -C ~/install/go -xzf go$VERSION.$OS-$ARCH.tar.gz
    
  • gde je VERSION - tekuća verzija (trenutno 1.12.4), OS - operativni sistem (npr. linux) a ARCH - arhitektura (npr. amd64)
  • podesiti PATH:

    export PATH=$PATH:~/install/go/
    

Workspace direktorijum

  • Direktorijum za Go kod
  • Podrazumevano $HOME/go
  • Ukoliko želite da promenite morate podesiti GOPATH varijablu

Testiranje instalacije

$ go version
go version go1.12.4 linux/amd64
  • Napraviti src/hello u workspace direktorijumu (dakle ~/go/src/hello)
  • Napraviti fajl hello.go sa sadržajem:

    package main
    
    import "fmt"
    
    func main() {
      fmt.Printf("Здраво, свете!\n")
    }
    
  • Preći u projektni folder, pokrenuti build i zatim startovati program:

    $ cd ~/go/src/hello
    $ go build
    $ ./hello
    Здраво, свете!
    

Kako pisati Go kod?

Uvod

Go alati podrazumevaju određene konvencije u organizaciji foldera i programskog koda.

Bazirano na How to Write Go Code

Workspaces

  • Obično se sav Go kod smešta na jedno mesto koje zovemo workspace
  • Na ovom mestu se nalazi veći broj repozitorijuma pod sistemima za kontrolu verzija (npr. git).
  • Svaki repozitorijum se sastoji od jednog ili više paketa
  • Svaki paket se sastoji od jednog ili više Go fajlova u jednom direktorijumu
  • Putanja (path) do direktorijuma paketa određuje njegov import path

Workspaces

bin/
    hello                          # command executable
    outyet                         # command executable
src/
    github.com/golang/example/
        .git/                      # Git repository metadata
  hello/
      hello.go               # command source
  outyet/
      main.go                # command source
      main_test.go           # test source
  stringutil/
      reverse.go             # package source
      reverse_test.go        # test source
    golang.org/x/image/
        .git/                      # Git repository metadata
  bmp/
      reader.go              # package source
      writer.go              # package source
    ... (many more repositories and packages omitted) ...

GOPATH

  • Podrazumevano $HOME/go na Unix-like sistemima ili %USERPOFILE%\go na Windows-u.
  • go env GOPATH komanda daje informaciju o tekućoj lokaciji tj. sadržaj GOPATH varijable ili podrazumevanu lokaciju ukoliko nije podešena.
  • Da bi instalirani Go programi bili dostupni:

    $ export PATH=$PATH:$(go env GOPATH)/bin
    

Import putanja (import path)

  • Kratke putanje za standardnu biblioteku. Na primer: fmt, net/http
  • Putanja je relativna u odnosu na $GOPATH/src
  • Mora biti jedinstvena
  • Dobra praksa je upotreba domena VCS hosting sajtova. Na primer:

    $GOPATH/src/github.com/user
    

Prvi Go program

$ mkdir $GOPATH/src/github.com/user/hello
  • Fajl hello.go
package main

import "fmt"

func main() {
  fmt.Printf("Здраво, свете!\n")
}
$ go install github.com/user/hello
# ili
$ cd $GOPATH/src/github.com/user/hello
$ go install
  • hello program će biti instaliran u $GOPATH/bin/
$ $GOPATH/bin/hello
Здраво, свете!
# ili samo
$ hello
Здраво, свете!
  • Sledeći korak je da promenu zabeležimo u sistemu za kontrolu verzija:
$ cd $GOPATH/src/github.com/user/hello
$ git init
Initialized empty Git repository in /home/user/work/src/github.com/user/hello/.git/
$ git add hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
1 file changed, 1 insertion(+)
  create mode 100644 hello.go

Prva Go biblioteka

$ mkdir $GOPATH/src/github.com/user/stringutil
// Package stringutil contains utility functions for working with strings.
package stringutil

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
  r := []rune(s)
  for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
    r[i], r[j] = r[j], r[i]
  }
  return string(r)
}
  • Provera ispravnosti biblioteke
$ go build github.com/user/stringutil
# ili ako smo već u folderu stringutil
$ go build
  • Modifikovati program hello.go
package main

import (
  "fmt"

  "github.com/user/stringutil"
)

func main() {
  fmt.Println(stringutil.Reverse("!етевс ,овардЗ"))
}
$ go install github.com/user/hello
$ hello
Здраво, свете!
bin/
  hello                 # command executable
src/
    github.com/user/
        hello/
            hello.go      # command source
        stringutil/
            reverse.go    # package source

Imenovanje paketa

  • Prvi iskaz u svakom Go fajlu mora biti:

    package name
    
  • …gde je name ime paketa koje se koristi pri import-u
  • Svi fajlovi koji pripadaju istom paketu moraju koristiti isto ime
  • Go konvencija je da je ime paketa poslednji element import putanje. Primer: ukoliko se paket importuje sa crypto/rot13 tada njegovo ime treba da bude rot13
  • Izvršne komande moraju biti u paketu main
  • Ne zahteva se da ime paketa bude jedinstveno kod svih paketa koji se importuju ali se zahteva da putanja bude

Promena imena pri import-u paketa

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin
  • Import paketa bez upotrebe (zbog side-effect-a)
import _ "lib/math"

Testiranje

  • Lightweight test okvir u paketu testing + go test komanda
  • Jedinični test se piše u fajlu koji se završava na _test.go i koji sadrži funkcije oblika TestXXX sa signaturom func (t *testing.T)
  • go test poziva sve funkcije i ako ona pozove t.Error ili t.Fail test se smatra neuspešnim
  • Fajl $GOPATH/src/github.com/user/stringutil/reverse_test.go
package stringutil

import "testing"

func TestReverse(t *testing.T) {
  cases := []struct {
    in, want string
  }{
    {"Hello, world", "dlrow ,olleH"},
    {"Hello, 世界", "界世 ,olleH"},
    {"", ""},
  }
  for _, c := range cases {
    got := Reverse(c.in)
    if got != c.want {
      t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
    }
  }
}
$ go test github.com/user/stringutil
ok  	github.com/user/stringutil 0.165s
# ili samo
$ go test
ok  	github.com/user/stringutil 0.165s

Udaljeni paketi (Remote packages)

  • Import putanja može biti u obliku URL-a do repozitorijuma za kontrolu verzija (npr. Git ili Mercurial)
  • go get alat će preuzeti kod iz udaljenog repozitorijuma ako nije dostupan u lokalnom radnom prostoru (workspace)
  • Zavisni paketi dostupni preko udaljenih repozitorijuma se takođe automatski preuzimaju
  • Za više informacija videti go help importpath

Go Tour

Interaktivno učenje Go jezika:

Pokretanje lokalno:

$ go get golang.org/x/tour
$ tour

Paketi, varijable, funkcije

Paketi

  • Svaki Go program se sastoji od paketa
  • Program počinje izvršavanje u paketu main i funkciji func main()
package main

import (
  "fmt"
  "math/rand"
)

func main() {
  fmt.Println("My favorite number is", rand.Intn(10))
}
  • Paket se referencira po poslednjoj komponenti import putanje (u prethodnom primeru rand).

Import iskaz

  • Navođenje paketa koji se koriste u posmatranom fajlu
  • Može se navesti više paketa unutar zagrada (tzv. factored import)
package main

import (
  "fmt"
  "math"
)

func main() {
  fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}

Eksportovana imena

Ime je eksportovano iz paketa (odnosno može se importovati u drugim paketima) ako počinje velikim slovom.

package main

import (
  "fmt"
  "math"
)

func main() {
  fmt.Println(math.pi)  // <-- treba math.Pi
}

Imenima koji počinju malim slovom ne može se pristupiti izvan paketa u kome su definisani.

Funkcije

  • Funkcije mogu imati nula ili više argumenata
package main

import "fmt"

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

func main() {
  fmt.Println(add(42, 13))
}
  • Ili kraće
package main

import "fmt"

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

func main() {
  fmt.Println(add(42, 13))
}

Višestruke povratne vrednosti

  • Funkcije mogu vratiti proizvoljan broj rezultata
package main

import "fmt"

func swap(x, y string) (string, string) {
  return y, x
}

func main() {
  a, b := swap("hello", "world")
  fmt.Println(a, b)
}

Imenovane povratne vrednosti

  • Povratne vrednosti mogu biti imenovane i u tom slučaju može se koristiti return iskaz bez argumenata (tzv. naked return)
package main

import "fmt"

func split(sum int) (x, y int) {
  x = sum * 4 / 9
  y = sum - x
  return
}

func main() {
  fmt.Println(split(17))
}

Varijable

  • var iskaz definiše varijable. Tip se navodi na kraju
  • var iskaz se može koristiti na nivou paketa ili funkcije
package main

import "fmt"

var c, python, java bool

func main() {
  var i int
  fmt.Println(i, c, python, java)
}

Varijable sa inicijalizatorima

  • var iskaz može imati inicijalizatore, jedan po varijabli
  • Ako se koristi inicijalizator, tip može da se izostavi jer može da se odredi na osnovu inicijalizatora
package main

import "fmt"

var i, j int = 1, 2

func main() {
  var c, python, java = true, false, "no!"
  fmt.Println(i, j, c, python, java)
}

Kratka deklaracija varijabli

  • Unutar funkcija, kraći oblik deklaracije baziran na := dodeli može da se koristi
package main

import "fmt"

func main() {
  var i, j int = 1, 2
  k := 3
  c, python, java := true, false, "no!"

  fmt.Println(i, j, k, c, python, java)
}
  • Ovo nije moguće koristiti van funkcije jer svaki iskaz mora početi sa ključnom rečju

Osnovni tipovi

  • Osnovni tipovi u Go-u su:
bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128
package main

import (
  "fmt"
  "math/cmplx"
)

var (
  ToBe   bool       = false
  MaxInt uint64     = 1<<64 - 1
  z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
  fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
  fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
  fmt.Printf("Type: %T Value: %v\n", z, z)
}
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)

Nulte vrednosti

  • Varijable deklarisane bez inicijalizatora se inicijalizuju na podrazumevane nulte vrednosti:
    • 0 za numeričke tipove
    • false za boolean tip
    • "" (prazan string) za string tip
package main

import "fmt"

func main() {
  var i int
  var f float64
  var b bool
  var s string
  fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
0 0 false ""

Konverzija tipova

  • Izraz oblika T(v) konvertuje vrednost v u tip T
  • Na primer:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
  • … ili jednostavnije:
i := 42
f := float64(i)
u := uint(f)
  • Za razliku od C-a u Go-u dodela između različitih tipova zahteva eksplicitnu konverziju
package main

import (
  "fmt"
  "math"
)

func main() {
  var x, y int = 3, 4
  var f float64 = math.Sqrt(float64(x*x + y*y))
  var z uint = uint(f)
  fmt.Println(x, y, z)
}

Inferencija tipova (Type inference)

  • Kada je desna strana iskaza dodele (bilo := bilo var =) tipizirana, leva strana će biti istog tipa
var i int
j := i // j is an int
  • Ali ako je na desnoj strani netipizirana numerička konstanta, tip zavisi od preciznosti konstante:
i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Konstante

  • Deklarisane kao varijable ali upotrebom ključne reči const
  • Mogu biti osnovnih tipova
  • Ne mogu se definisati upotrebom :=
package main

import "fmt"

const Pi = 3.14

func main() {
  const World = "世界"
  fmt.Println("Hello", World)
  fmt.Println("Happy", Pi, "Day")

  const Truth = true
  fmt.Println("Go rules?", Truth)
}

Numeričke konstante

  • Vrednosti proizvoljne preciznosti (arbitrary-precision)
  • Kod netipiziranih konstanti tip se određuje na osnovu konteksta
package main

import "fmt"

const (
  // Create a huge number by shifting a 1 bit left 100 places.
  // In other words, the binary number that is 1 followed by 100 zeroes.
  Big = 1 << 100
  // Shift it right again 99 places, so we end up with 1<<1, or 2.
  Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
  return x * 0.1
}

func main() {
  fmt.Println(needInt(Small))
  fmt.Println(needFloat(Small))
  fmt.Println(needFloat(Big))
  fmt.Println(needInt(Big))  // <-- constant overflow
}
const Huge = 1e1000

# Sledeća linija ne može da se kompajlira
# Greška je 'constant 1.00000e+1000 overflows float64'
fmt.Println(Huge)

# Ali ovo radi bez problema jer se kalkulacija obavlja od strane kompajlera
# u vreme kompajliranja
fmt.Println(Huge / 1e999)

Enumerisane konstante (iota)

  • Kada nam je potrebna enumeracija
const (
    CategoryBooks = iota // 0
    CategoryHealth       // 1
    CategoryClothing     // 2
)
  • ili u kombinaciji sa tipom (da bi kompajler mogao da spreči greške sa tipovima)
type Stereotype int

const (
    TypicalNoob Stereotype = iota // 0
    TypicalHipster                // 1
    TypicalUnixWizard             // 2
    TypicalStartupFounder         // 3
)
  • Ako želimo da preskočimo vrednosti
type AudioOutput int

const (
    OutMute AudioOutput = iota // 0
    OutMono                    // 1
    OutStereo                  // 2
    _
    _
    OutSurround                // 5
)
  • Možemo kreirati i izraze sa iota
type Allergen int

const (
    IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001
    IgChocolate                 // 1 << 1 which is 00000010
    IgNuts                      // 1 << 2 which is 00000100
    IgStrawberries              // 1 << 3 which is 00001000
    IgShellfish                 // 1 << 4 which is 00010000
)

Iskazi kontrole toka: for, if, else, switch i defer

For iskaz

  • Go ima samo jedan iskaz za petlje – for
  • Tri komponente:
    • init iskaz – izvršava se pre prve iteracije
    • uslov – evaluira se pre svake iteracije i u zavisnosti od rezultata ciklus se izvršava ili se petlja prekida
    • post iskaz – izvršava se na kraju svake iteracije
package main

import "fmt"

func main() {
  sum := 0
  for i := 0; i < 10; i++ {
    sum += i
  }
  fmt.Println(sum)
}
  • Zagrade () se ne navode kao u nekim drugim jezicima ali je navođenje {} za telo petlje obavezno
  • Varijable kreirane u init iskazu su dostupne samo unutar petlje
  • init i post iskazi su opcioni
package main

import "fmt"

func main() {
  sum := 1
  for ; sum < 1000; {
    sum += sum
  }
  fmt.Println(sum)
}

for je while u Go-u

  • Kada nemamo init i post iskaz možemo izostaviti ; i dobijamo ekvivalent while petlje u drugim jezicima
package main

import "fmt"

func main() {
  sum := 1
  for sum < 1000 {
    sum += sum
  }
  fmt.Println(sum)
}

"Beskonačna" petlja

  • Ako se izostavi i uslov dobijamo beskonačnu petlju
package main

func main() {
  for {
  }
}

Uslovi – if iskaz

  • Kao i kod for i kod if iskaza zagrade nije potrebno navoditi
package main

import (
  "fmt"
  "math"
)

func sqrt(x float64) string {
  if x < 0 {
    return sqrt(-x) + "i"
  }
  return fmt.Sprint(math.Sqrt(x))
}

func main() {
  fmt.Println(sqrt(2), sqrt(-4))
}

if sa kratkim iskazom

  • Kao i for i if može da ima kratak iskaz (najčešće :=) koji se izvršava pre uslova
  • Varijable deklarisane u ovom iskazu su dostupne samo unutar if iskaza
package main

import (
  "fmt"
  "math"
)

func pow(x, n, lim float64) float64 {
  if v := math.Pow(x, n); v < lim {
    return v
  }
  return lim
}

func main() {
  fmt.Println(
    pow(3, 2, 10),
    pow(3, 3, 20),
  )
}

if i else

  • Varijable deklarisane u if kratkom iskazu su dostupne i u opcionom else bloku
package main

import (
  "fmt"
  "math"
)

func pow(x, n, lim float64) float64 {
  if v := math.Pow(x, n); v < lim {
    return v
  } else {
    fmt.Printf("%g >= %g\n", v, lim)
  }
  // can't use v here, though
  return lim
}

func main() {
  fmt.Println(
    pow(3, 2, 10),
    pow(3, 3, 20),
  )
}
27 >= 20
9 20

switch iskaz

  • Kraći način pisanja sekvence if/else iskaza. Izvršava prvi case blok gde je vrednost jednaka vrednošću izraza uslova
  • Može imati kratak iskaz kao i if
package main

import (
  "fmt"
  "runtime"
)

func main() {
  fmt.Print("Go runs on ")
  switch os := runtime.GOOS; os {
  case "darwin":
    fmt.Println("OS X.")
  case "linux":
    fmt.Println("Linux.")
  default:
    // freebsd, openbsd,
    // plan9, windows...
    fmt.Printf("%s.\n", os)
  }
}
  • Za razliku od drugih jezika izvršava se samo jedan case blok (nema "propadanja"). Takođe case prihvata izraz koji ne mora biti konstanta i čija vrenost ne mora biti numerička

Evaluacija switch iskaza

  • Evaluacija case blokova ide od vrha prema dnu dok se ne nađe prvi blok čija vrednost je jednaka zadatom uslovu
  • f() se ne poziva ako je i==0
switch i {
case 0:
case f():
}
package main

import (
  "fmt"
  "time"
)

func main() {
  fmt.Println("When's Saturday?")
  today := time.Now().Weekday()
  switch time.Saturday {
  case today + 0:
    fmt.Println("Today.")
  case today + 1:
    fmt.Println("Tomorrow.")
  case today + 2:
    fmt.Println("In two days.")
  default:
    fmt.Println("Too far away.")
  }
}

switch bez uslova

  • Ekvivalentno sa switch true
  • Ćistiji način pisanja dugih if-then-else lanaca
package main

import (
  "fmt"
  "time"
)

func main() {
  t := time.Now()
  switch {
  case t.Hour() < 12:
    fmt.Println("Good morning!")
  case t.Hour() < 17:
    fmt.Println("Good afternoon.")
  default:
    fmt.Println("Good evening.")
  }
}

defer iskaz

  • Odlaže izvršavanje funkcije dok se ne vratimo iz funkcije u kojoj se nalazimo
  • Parametri funkcije se evaluiraju na mestu poziva defer ali se ciljna funkcija ne poziva do povratka
package main

import "fmt"

func main() {
  defer fmt.Println("world")

  fmt.Println("hello")
}
hello
world

Stekovanje defer poziva

  • defer pozivi se smeštaju na stek i po povratku funcije se izvršavaju u LIFO redosledu
package main

import "fmt"

func main() {
  fmt.Println("counting")

  for i := 0; i < 10; i++ {
    defer fmt.Println(i)
  }

  fmt.Println("done")
}
counting
done
9
8
...

struct, slice i map

Pokazivači (Pointers)

  • Memorijska adresa vrednosti varijable
  • *T je pokazivač na vrednost tipa T
  • & operator vraća pokazivač na zadati argument/vrednost

    i := 42
    p = &i  // p je pokazivač na vrednost 42
    
  • * operator označava vrednost na koju pokazivač pokazuje

    fmt.Println(*p) // čitanje i vrednosti kroz pokazivač p
    *p = 21         // postavljanje i vrednosti kroz pokazivač p
    
    • Ovo se još naziva i dereferenciranje ili indirekcija
    • Za razliku od jezika C, Go nema pokazivačku aritmetiku
package main

import "fmt"

func main() {
  i, j := 42, 2701

  p := &i         // point to i
  fmt.Println(*p) // read i through the pointer
  *p = 21         // set i through the pointer
  fmt.Println(i)  // see the new value of i

  p = &j         // point to j
  *p = *p / 37   // divide j through the pointer
  fmt.Println(j) // see the new value of j
}

Strukture (struct)

  • Kolekcija polja
package main

import "fmt"

type Vertex struct {
  X int
  Y int
}

func main() {
  fmt.Println(Vertex{1, 2})
}
  • Poljima se pristupa upotrebom . operatora
package main

import "fmt"

type Vertex struct {
  X int
  Y int
}

func main() {
  v := Vertex{1, 2}
  v.X = 4
  fmt.Println(v.X)
}

Pokazivači na strukture

  • Poljima strukture se može pristupiti preko pokazivača na strukturu
  • Sintaksno, ako imamo pokazivač p na strukturu, polju X bi mogli pristupiti sa (*p).X
  • Pošto je ovakva sintaksa teža za korišćenje uvedena je prečica p.X tj. nije potrebno eksplicitno dereferenciranje

    package main
    
    import "fmt"
    
    type Vertex struct {
      X int
      Y int
    }
    
    func main() {
      v := Vertex{1, 2}
      p := &v
      p.X = 1e9
      fmt.Println(v)
    }
    

struct literali

  • Kreiranje strukture listanjem vrednosti njenih polja
  • Moguće je koristiti sintaksu Name: za postavljanje vrednosti polja i u tom slučaju redosled je irelevantan
  • Ukoliko se koristi operator & vraća se pokazivač na strukturu
package main

import "fmt"

type Vertex struct {
  X, Y int
}

var (
  v1 = Vertex{1, 2}  // has type Vertex
  v2 = Vertex{X: 1}  // Y:0 is implicit
  v3 = Vertex{}      // X:0 and Y:0
  p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
  fmt.Println(v1, p, v2, v3)
}

Nizovi (Arrays)

  • [n]T – niz od n elemenata tipa T
  • var a [10]int – niz od 10 elemenata tipa int
  • Nizovi su fiksne veličine
package main

import "fmt"

func main() {
  var a [2]string
  a[0] = "Hello"
  a[1] = "World"
  fmt.Println(a[0], a[1])
  fmt.Println(a)

  primes := [6]int{2, 3, 5, 7, 11, 13}
  fmt.Println(primes)
}

Isečci (Slices)

  • Niz je fiksne dužine
  • Isečak je "prozor" na niz koji ima dinamičku veličinu
  • []T – isečak tipa T
  • Isečak se formira iznad niza na sledeći način:

    a[low : high]
    
  • Ovo formira polu-otvoren interval elemenata uključujući prvi ali isključujući poslednji

    package main
    
    import "fmt"
    
    func main() {
      primes := [6]int{2, 3, 5, 7, 11, 13}
    
      var s []int = primes[1:4]
      fmt.Println(s)
    }
    

Isečci kao pokazivači

  • Isečci se ponašaju kao pokazivači na nizove ispod njih
  • Izmena elementa kroz isečak menja elemente niza
package main

import "fmt"

func main() {
  names := [4]string{
    "John",
    "Paul",
    "George",
    "Ringo",
  }
  fmt.Println(names)

  a := names[0:2]
  b := names[1:3]
  fmt.Println(a, b)

  b[0] = "XXX"
  fmt.Println(a, b)
  fmt.Println(names)
}
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]

Literali za isečke

[3]bool{true, true, false}
[]bool{true, true, false}
package main

import "fmt"

func main() {
  q := []int{2, 3, 5, 7, 11, 13}
  fmt.Println(q)

  r := []bool{true, false, true, true, false, true}
  fmt.Println(r)

  s := []struct {
    i int
    b bool
  }{
    {2, true},
    {3, false},
    {5, true},
    {7, true},
    {11, false},
    {13, true},
  }
  fmt.Println(s)
}

Podrazumevane granice intervala

  • Kada se kreira isečak niza moguće je izostaviti bilo koju granicu.
  • Podrazumevana donja granica je 0
  • Podrazumevana gornja granica je dužina niza
  • Ekvivalentni izrazi:

    a[0:10]
    a[:10]
    a[0:]
    a[:]
    
package main

import "fmt"

func main() {
  s := []int{2, 3, 5, 7, 11, 13}

  s = s[1:4]
  fmt.Println(s)

  s = s[:2]
  fmt.Println(s)

  s = s[1:]
  fmt.Println(s)
}
[3 5 7]
[3 5]
[5]

Dužina i kapacitet isečka

  • Isečak ima dužinu i kapacitet
  • Dužina je broj elemenata isečka. Kapacitet je broj elemenata počevši od prvog elementa isečka do poslednjeg elementa potpornog niza.
  • dužina i kapacitet isečka s: len(s), cap(s)
  • Dužina isečka se može povećati ponovnim isecanjem (reslicing) u skladu sa kapacitetom
package main

import "fmt"

func main() {
  s := []int{2, 3, 5, 7, 11, 13}
  printSlice(s)

  // Slice the slice to give it zero length.
  s = s[:0]
  printSlice(s)

  // Extend its length.
  s = s[:4]
  printSlice(s)

  // Drop its first two values.
  s = s[2:]
  printSlice(s)
}

func printSlice(s []int) {
  fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

Prazni (nil) isečci

  • Nula vrednost za isečak je nil
  • Ovakav isečak ima dužinu i kapacitet 0 i nema potporni niz
package main

import "fmt"

func main() {
  var s []int
  fmt.Println(s, len(s), cap(s))
  if s == nil {
    fmt.Println("nil!")
  }
}
[] 0 0
nil!

Kreiranje isečaka sa make

  • make funkcija alocira niz sa nultim vrednostima i vraća njegov isečak

    a := make([]int, 5)  // len(a)=5
    
  • moguće je definisati i kapacitet

     b := make([]int, 0, 5) // len(b)=0, cap(b)=5
    
    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:]      // len(b)=4, cap(b)=4
    
package main

import "fmt"

func main() {
  a := make([]int, 5)
  printSlice("a", a)

  b := make([]int, 0, 5)
  printSlice("b", b)

  c := b[:2]
  printSlice("c", c)

  d := c[2:5]
  printSlice("d", d)
}

func printSlice(s string, x []int) {
  fmt.Printf("%s len=%d cap=%d %v\n",
    s, len(x), cap(x), x)
}
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]

Isečak isečaka

  • Isečak može da sadrži bilo koji tip, uključujući i druge isečke
package main

import (
  "fmt"
  "strings"
)

func main() {
  // Create a tic-tac-toe board.
  board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
  }

  // The players take turns.
  board[0][0] = "X"
  board[2][2] = "O"
  board[1][2] = "X"
  board[1][0] = "O"
  board[0][2] = "X"

  for i := 0; i < len(board); i++ {
    fmt.Printf("%s\n", strings.Join(board[i], " "))
  }
}

Dodavanje na isečak

func append(s []T, vs ...T) []T
  • Prvi parametar je isečak, ostali su vrednosti koje se dodaju
  • Povratna vrednost je novi isečak koji sadrži nove elemente
  • Ako potporni niz nema dovoljan kapacitet alocira se novi
package main

import "fmt"

func main() {
  var s []int
  printSlice(s)

  // append works on nil slices.
  s = append(s, 0)
  printSlice(s)

  // The slice grows as needed.
  s = append(s, 1)
  printSlice(s)

  // We can add more than one element at a time.
  s = append(s, 2, 3, 4)
  printSlice(s)
}

func printSlice(s []int) {
  fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]

range

  • range forma for petlje iterira kroz elemente isečaka ili mape
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
  for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
  }
}
  • Ukoliko ne koristite indeks pri iteraciji moguće ga je ignorisati upotrebom specijalnog imena _

    for i, _ := range pow
    for _, value := range pow
    
  • Ako vam treba samo indeks možete izostaviti drugu varijablu:

    for i := range pow
    
    package main
    
    import "fmt"
    
    func main() {
      pow := make([]int, 10)
      for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
      }
      for _, value := range pow {
        fmt.Printf("%d\n", value)
      }
    }
    

Mape

  • Mapiranje ključeva na vrednosti – asocijativni niz
  • Nula vrednost je nil
  • nil mapa nema ključeve niti se ključevi mogu dodati
  • make funkcija vraća inicijalizovanu mapu spremnu za upotrebu
package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m map[string]Vertex

func main() {
  m = make(map[string]Vertex)
  m["Bell Labs"] = Vertex{
    40.68433, -74.39967,
  }
  fmt.Println(m["Bell Labs"])
}

Literali za mape

package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m = map[string]Vertex{
  "Bell Labs": Vertex{
    40.68433, -74.39967,
  },
  "Google": Vertex{
    37.42202, -122.08408,
  },
}

func main() {
  fmt.Println(m)
}
  • Ime tipa literala se može izostaviti:
package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m = map[string]Vertex{
  "Bell Labs": {40.68433, -74.39967},
  "Google":    {37.42202, -122.08408},
}

func main() {
  fmt.Println(m)
}

Izmena vrednosti mapa

  • postavljanje vrednosti
m[key] = elem
  • čitanje vrednosti
elem = m[key]
  • brisanje
delete(m, key)
  • čitanje i provera da li element postoji
elem, ok = m[key]

Ako element postoji ok će imati vrednost true inače false

package main

import "fmt"

func main() {
  m := make(map[string]int)

  m["Answer"] = 42
  fmt.Println("The value:", m["Answer"])

  m["Answer"] = 48
  fmt.Println("The value:", m["Answer"])

  delete(m, "Answer")
  fmt.Println("The value:", m["Answer"])

  v, ok := m["Answer"]
  fmt.Println("The value:", v, "Present?", ok)
}

Funkcije kao vrednosti

  • Funkcije se u Go-u vrednosti takođe.
  • Mogu se čuvati kao varijable, prosleđivati kao parametri funkcije ili dobijati nazad kao povratne vrednosti
package main

import (
  "fmt"
  "math"
)

func compute(fn func(float64, float64) float64) float64 {
  return fn(3, 4)
}

func main() {
  hypot := func(x, y float64) float64 {
    return math.Sqrt(x*x + y*y)
  }
  fmt.Println(hypot(5, 12))

  fmt.Println(compute(hypot))
  fmt.Println(compute(math.Pow))
}
13
5
81

Zatvorenja (Closures)

  • Go funkcije mogu biti zatvorenja u smislu da funkcija može da referencira vrednosti koje se nalaze izvan nje na mestu njenog kreiranja čak i kada se pozove sa drugog mesta. Kažemo da su referencirane vrednosti "vezane" za funkciju (bounded)
package main

import "fmt"

func adder() func(int) int {
  sum := 0
  return func(x int) int {
    sum += x
    return sum
  }
}

func main() {
  pos, neg := adder(), adder()
  for i := 0; i < 10; i++ {
    fmt.Println(
      pos(i),
      neg(-2*i),
    )
  }
}

TODO Metode i interfejsi

Metode i interfejsi

  • Go nema klase ali se mogu definisati metode nad tipovima
  • Metoda je funkcija koja ima specijalni receiver parametar
package main

import "fmt"

func adder() func(int) int {
  sum := 0
  return func(x int) int {
    sum += x
    return sum
  }
}

func main() {
  pos, neg := adder(), adder()
  for i := 0; i < 10; i++ {
    fmt.Println(
      pos(i),
      neg(-2*i),
    )
  }
}

Metode su funkcije

  • Metode se u osnovi ponašaju kao obične funkcije
package main

import (
  "fmt"
  "math"
)

type Vertex struct {
  X, Y float64
}

func Abs(v Vertex) float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
  v := Vertex{3, 4}
  fmt.Println(Abs(v))
}
  • Metode se ne moraju definisati samo nad strukturama. Na primer:
package main

import (
  "fmt"
  "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
  if f < 0 {
    return float64(-f)
  }
  return float64(f)
}

func main() {
  f := MyFloat(-math.Sqrt2)
  fmt.Println(f.Abs())
}

TODO Konkurencija

Reference