Wprowadzenie

Go, znany także jako Golang, to język programowania stworzony przez Google. Jest to język open source charakteryzujący się wyjątkową wydajnością i efektywnością. Jest idealny do tworzenia oprogramowania serwerowego, narzędzi dla programistów i aplikacji internetowych. Ze względu na swoją prostotę, Go zyskuje coraz większą popularność w tworzeniu skalowalnych i wydajnych aplikacji internetowych oraz mikrousług.

Przydatne linki

Hello World!

package main

import "fmt"

func main() {
  message := greetMe("world")
  fmt.Println(message)
}

func greetMe(name string) string {
  return "Hello, " + name + "!"
}
$ go build

Zmienne

var msg string
var msg = "Hello, world!"
var msg string = "Hello, world!"
var x, y int
var x, y int = 1, 2
var x, msg = 1, "Hello, world!"
msg = "Hello"

// Deklaracja listy
var (
  x int
  y = 20
  z int = 30
  d, e = 40, "Hello"
  f, g string
)

msg := "Hello"
x, msg := 1, "Hello"

Stałe

Stałe mogą przyjmować wartości liczbowe, tekstowe, logiczne lub numeryczne.

const Phi = 1.618
const Size int64 = 1024
const x, y = 1, 2
const (
  Pi = 3.14
  E  = 2.718
)
const (
  Sunday = iota
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)

Podstawowe typy danych

String

str := "Hello"

str := `Multiline
string`

Liczby

num := 3          // int
num := 3.         // float64
num := 3 + 4i     // complex128
num := byte('a')  // byte (alias for uint8)

Inne typy liczbowe

var u uint = 7        // uint (bez znaku)
var p float32 = 22.7  // float 32-bitowy

Tablice

Tablice mają zdefiniowany rozmiar.

// var numbers [5]int
numbers := [...]int{0, 0, 0, 0, 0}

Wycinki (Slices)

Wycinki mają dynamiczną wielkość w przeciwieństwie do tablic.

slice := []int{2, 3, 4}

slice := []byte("Hello")

Wskaźniki

Wskaźniki wskazują na lokalizację w pamięci zmiennej.

func main() {
  b := *getPointer()
  fmt.Println("Wartość to", b)
}

func getPointer() *int {
  a := 234
  return &a
}

a := new(int)
*a = 234

Konwersje typów

i := 2
f := float64(i)
u := uint(i)

Kontrola przepływu

Warunki

if day == "niedziela" || day == "sobota" {
  odpocznij()
} else if day == "poniedziałek" && jestesZmęczony() {
  jęk()
} else {
  pracuj()
}

Instrukcje w warunkach

Warunek w instrukcji “if” może być poprzedzony instrukcją przed średnikiem (;). Zmienne zadeklarowane w tej instrukcji są dostępne tylko w jej zakresie.

if _, err := zrobCos(); err != nil {
  fmt.Println("Ups!")
}

Instrukcja “switch”

switch day {
  case "niedziela":
    // przypadki nie "przechodzą" domyślnie
    fallthrough

  case "sobota":
    odpocznij()

  default:
    pracuj()
}

Pętla “for”

for count := 0; count <= 10; count++ {
  fmt.Println("Licznik wynosi", count)
}

Pętla “for” z zakresem (for-range)

entry := []string{"Jack", "John", "Jones"}
for i, val := range entry {
  fmt.Printf("Na pozycji %d znajduje się postać %s\n", i, val)
}

Pętla “while”

n := 0
x := 42
for n != x {
  n := zgadnij()
}

Funkcje

W języku Go, funkcje są obiektami pierwszej klasy.

Lambdy

myfunc := func() bool {
  return x > 10000
}

Wiele wartości zwracanych

a, b := getMessage()

func getMessage() (a string, b string) {
  return "Hello", "World"
}

Nazwane wartości zwracane

Poprzez zdefiniowanie nazw wartości zwracanych w sygnaturze funkcji, zwrócenie (bez argumentów) spowoduje zwrócenie zmiennych o tych nazwach.

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

Paczki

Importowanie

import "fmt"
import "math/rand"

import (
  "fmt"        // daje fmt.Println
  "math/rand"  // daje rand.Intn
)

Oba przypadki są takie same.

Aliasy

import r "math/rand"

r.Intn()

Paczki

Każdy plik paczki musi zaczynać się od słowa kluczowego ‘package’.

package hello

Eksportowanie nazw

Nazwy eksportowane zaczynają się od wielkich liter.

func Hello() {
  ···
}

Współbieżność

Goroutines

Kanały są bezpiecznymi obiektami komunikacji używanymi w gorutynach.

func main() {
  // Kanał
  ch := make(chan string)

  // Rozpocznij gorutyny równolegle
  go push("Moe", ch)
  go push("Larry", ch)
  go push("Curly", ch)

  // Odczytaj 3 wyniki
  // (Ponieważ nasze gorutyny są równoległe,
  // kolejność nie jest gwarantowana!)
  fmt.Println(<-ch, <-ch, <-ch)
}
 
func push(name string, ch chan string) {
  msg := "Hej, " + name
  ch <- msg
}

Kanały buforowane

Kanały buforowane ograniczają ilość wiadomości, które mogą przechowywać.

ch := make(chan int, 2)
ch <- 1
ch <- 2
ch <- 3
// błąd krytyczny:
// wszystkie gorutyny są uśpione - deadlock!

Zamykanie kanałów

ch <- 1
ch <- 2
ch <- 3
close(ch)

Iteruje przez kanał do momentu jego zamknięcia.

for i := range ch {
  ···
}

Kanał jest zamknięty, jeśli ok == false.

v, ok := <-ch

WaitGroup

import "sync"

func main() {
  var wg sync.WaitGroup
  
  for _, item := range itemList {
    // Zwiększ licznik WaitGroup
    wg.Add(1)
    go doOperation(&wg, item)
  }
  // Czekaj, aż gorutyny zakończą pracę
  wg.Wait()
}

func doOperation(wg *sync.WaitGroup, item string) {
  defer wg.Done()
  // Wykonaj operację na elemencie
  // ...
}

WaitGroup oczekuje na zakończenie zbioru gorutyn. Główna gorutyna wywołuje metodę Add, aby ustawić liczbę gorutyn, na które ma czekać. Każda gorutyna wywołuje wg.Done() po zakończeniu swojej pracy.

Kontrola błędów

Defer

“Defer” w języku Go jest mechanizmem, który pozwala na opóźnione wykonanie funkcji do momentu zakończenia otaczającej ją funkcji. Argumenty są ewaluowane natychmiast, ale samo wywołanie funkcji jest opóźnione do późniejszego czasu.

func main() {
  defer fmt.Println("Gotowe")
  fmt.Println("Pracuję...")
}

Funkcje opóźnione

func main() {
  defer func() {
    fmt.Println("Gotowe")
  }()
  fmt.Println("Pracuję...")
}

Lambdy (anonimowe funkcje) są lepiej dostosowane do bloków “defer”.

func main() {
  var d = int64(0)
  defer func(d *int64) {
    fmt.Printf("& %v Unix Sec\n", *d)
  }(&d)
  fmt.Print("Gotowe ")
  d = time.Now().Unix()
}

Blok “defer” używa aktualnej wartości zmiennej “d”, chyba że użyjemy wskaźnika, aby uzyskać ostateczną wartość na końcu funkcji “main”.

Struktury

Definiowanie

type Vertex struct {
  X int
  Y int
}

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

Literały

v := Vertex{X: 1, Y: 2}

// Nazwy pól mogą być pominięte
v := Vertex{1, 2}

// Pole Y jest domyślne
v := Vertex{X: 1}

Używanie wskaźników do struktur

v := &Vertex{1, 2}
v.X = 2

Odpowiednie wyrażenie v.X jest takie samo jak (*v).X, gdy v jest wskaźnikiem.

Metody

Odbiorcy

type Vertex struct {
  X, Y float64
}

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

v := Vertex{1, 2}
v.Abs()

W języku Go nie ma klas, ale można definiować funkcje z odbiorcami.

Mutacje

func (v *Vertex) Scale(f float64) {
  v.X = v.X * f
  v.Y = v.Y * f
}

v := Vertex{6, 12}
v.Scale(0.5)
// `v` zostanie zaktualizowane

Poprzez zdefiniowanie odbiorcy jako wskaźnika (*Vertex), można dokonywać mutacji danych.

Interfejsy

Podstawowy interfejs

type Shape interface {
  Area() float64
  Perimeter() float64
}

Struktury

type Rectangle struct {
  Length, Width float64
}

Struktura Rectangle implementuje interfejs Shape w sposób domyślny, poprzez zaimplementowanie wszystkich jego metod.

Metody

func (r Rectangle) Area() float64 {
  return r.Length * r.Width
}

func (r Rectangle) Perimeter() float64 {
  return 2 * (r.Length + r.Width)
}

Metody zdefiniowane w interfejsie Shape są zaimplementowane w strukturze Rectangle.

Przykład interfejsu

func main() {
  var r Shape = Rectangle{Length: 3, Width: 4}
  fmt.Printf("Typ r: %T, Pole: %v, Obwód: %v.", r, r.Area(), r.Perimeter())
}