Der Golang-Spicker

Aus archium
(Weitergeleitet von GolangCheatSheet)
Wechseln zu:Navigation, Suche

Inhaltsverzeichnis

Programmiersprache Go/Golang (Spickzettel, "Cheat Sheet")

Warum Go? Achtung - subjektiv!

Das offizielle Maskottchen – der Go-Gopher von Renee French

Bei Betrachtung der drei populären Rankings für Programmiersprachen (Tiobe-Index, PyPl-Index und The RedMonk Programming Language Rankings) fällt trotz der deutlichen Abweichungen bei Ermittlung der Rankings auf, daß langjährige Platzhirsche wie C/C++ und Java stark an Beliebtheit eingebüßt haben. Eine ganze Anzahl junger Compilersprachen ist nachgerückt, die nun wieder mehr auf Typsicherheit und Performanceoptimierung Wert legen, als wir es in den letzten Jahren mit dynamischen Interpreter-Sprachen wie Python, PHP und Perl gewohnt waren. Eine Ursache hierfür mag tatsächlich die verlangsamte Performancesteigerung bei Hardwareentwicklungen sein, die dazu führt, daß (wie schon vor 30 Jahren) Leistungssteigerungen durch Codeoptimierungen erzielt werden müssen, wie Edoardo Paolo Scalafiotti auf Hackernoon am 16.10.2016 sehr eindrücklich beschrieben hat.

Interessant ist aber auch, daß es zwar die großen IT-Riesen sind, die sich die Entwicklung eigener Sprachen leisten, doch glücklicherweise können sie es sich trotzdem nicht erlauben, diese Entwicklung nicht unter eine OpenSource-Lizenz zu stellen. Für besonders zukunftsträchtig halte ich die Sprachen Swift (von Apple), Go (von Google) und Rust (von Mozilla). Nachfolgend werden die Basis-Elemente der Sprache Go kompakt zusammengefaßt. Für Go spricht meiner persönlichen Ansicht nach im Vergleich zu Swift und Rust der ressourcenschonende und zugleich sehr ästhetische Minimalismus, sowie die neu bzw. anders erdachte Objektorientierung, die dazu führt, daß wir gewohnte Konstrukte wie die Klassen-Definition, aber auch try-catch-finally-Blöcke zum Abfangen von Exceptions nicht mehr benötigen.[1] Go vereint u.a. die Typsicherheit und das Package-Modell von Java, die pragmatische Syntax von Python und die Pointer von C. Auch die Dokumentation ist sagenhaft transparent. Und bei keiner anderen Sprache wird die Nebenläufigkeit und die Kommunikation zwischen parallel laufenden Routinen so trivial wie bei Go.[2]

Compiler

Gc

Gc ist der Go-Compiler. Seit Version 1.5. wurde er selber in Go geschrieben. Er erzeugt statische Binaries, ermöglicht aber auch die direkte Ausführung des Programmes, wie bei Scriptsprachen!

Erzeuge ein statisches Binary

$ go build byebyeJava.go
$ ./byebyeJava

Führe das "Script" sofort aus

$ go run byebyeJava.go

Kompiliere ein externes Package

$ go install [packages]

Lade externe Packages aus einem Repository

$ go get -u github.com/golang/lint/golint
$ go get github.com/therecipe/qt

Cross-Compiling

In Linux eine 32bit-.exe-Datei für Windows erzeugen
$ GOOS=windows GOARCH=i386 go build byebyeJava.go
In Linux eine 64bit-.exe-Datei für Windows erzeugen
$ GOOS=windows GOARCH=amd64 go build byebyeJava.go
Eine ausführbare Datei für Android erzeugen
$ GOOS=linux GOARCH=arm GOARM=7 go build byebyeJava.go
Eine ausführbare Datei für Raspberry PI3 erzeugen
$ GOOS=linux GOARCH=arm64 go build byebyeJava.go

Siehe https://github.com/golang/go/wiki/GoArm

In Windows eine ausführbare 64bit Datei für Linux erzeugen
$ GOOS=linux GOARCH=amd64 go build byebyeJava.go

Einblick in den Assembler-Code

Siehe https://golang.org/doc/asm

$ go build -gcflags -S byebyeJava.go

Gccgo

Gccgo ist ein Go-Frontend für die GNU Compiler Collection (GCC). Der Compiler wurde in C++ geschrieben. Gccgo hinkt dem Gc bei der Implementierung des Sprachumfangs hinterher[3] Der Gccgo erzeugt jedoch auch dynamisch gelinkte Binaries!

$ gccgo -Wall -O2 -o byebyeJava byebyeJava.go
$ ./byebyeJava

Packages

Verzeichnisstruktur

Wichtige Umgebungsvariablen

Installation der Go-Systemdateien
$ export GOROOT='/usr/local/go'
Workspace für den eigenen Code
$ export GOPATH='/home/me/workspace/'

Verzeichnisse, die Go im Workspace anlegt

src
Source-Codes (Endung .go)
Dieses Verzeichnis enthält in Unterverzeichnissen die Packages. Darin enthalten die eigenen Source-Codes.
bin
Binärcode (Ohne Endung)
Dieses Verzeichnis enthält die ausführbaren Programme mit beliebigem Namen.
pkg
Binärcode (Endung .a)
Dieses Verzeichnis enthält die externen Package-Objekte in kompillierter Form.

Packages von außen

Import

package main

import (
	"fmt"
	_ "image" // Geladen, aber nicht genutzt (keine Fehlermeldung)
	"math"
	"math/rand"
	sc "strconv"
	. "strings"
	t "time"
)

/*
// Alternative Schreibweise
import "fmt"
import _ "image"
*/

func main() {
	fmt.Println(math.Sqrt(9))
	rand.Seed(t.Now().UnixNano())        // Der Zufallsgenerator benötigt veränderlichen Input
	fmt.Println(rand.Intn(100))          // Und nicht etwa: math/rand.Intn(100)
	fmt.Println(ToLower("BYE BYE JAVA")) // Und nicht: fmt.Println(strings.ToLower("BYE BYE JAVA"))
	fmt.Printf("%s ist jetzt ein String\n", sc.FormatFloat(0.12345, 'G', -1, 64))
}

Packages von innen

Benennung

package myPackageName // Die Deklaration des Packages erfolgt immer in der Kopfzeile eines Source-Dokuments
package main // Executables müssen immer zum Package *main* gehören
package rand // Der package name ist immer der letzte Name des Import-Pfades. (import path math/rand => package rand)

Sichtbarkeit von Variablen und Funktionen

  • Identifier, die mit Großbuchstaben beginnen, sind public, sind also in anderen packages sichtbar
  • Identifier, die mit Kleinbuchstaben beginnen, sind private, bleiben also für andere packages unsichtbar

Standard-Funktionen

func main(){} // Ausführbare Programme müssen immer die Funktion main() besitzen
func init(){} // Die Funktion init() steht innerhalb eines Packages und wird beim Laden des Packages ausgeführt.

Funktionen

Einfache Funktionen

// Parameterübergabe ...
func functionName0si(param1 string, param2 int) {}

// ... kann bei gleichem Typ vereinfacht dargestellt werden.
func functionName0ii(param1, param2 int) {}

// Deklaration des Rückgabewertes erfolgt hinter der Funktion ...
func functionName1() int {
	return 123
}

// ... und kann *mehrere* Parameter zurückgeben!
func functionName2() (int, string) {
	return 123, "Blabla"
}

var x, y = functionName2()

Rückgabeparameter können auch benannt werden, dann erhält die return-Anweisung keine Argumente:

func functionName2() (i int, s string) {
	i = 123
	s = "Blabla"
	return // zurückgegeben werden i und s
}

var x, y = functionName2()

Veränderliche Parameter (Variadic Functions)

  • Drei Punkte ... unmittelbar vor dem Typ bei der Funktionsdefinition bereiten die Funktion auf die Übernahme einer unbestimmten Anzahl von Parametern dieses Typs vor. Die Funktion wird danach wie jede andere Funktion aufgerufen, nur mit dem Unterschied, daß wir ihr beliebig viele Argumente übergeben können.
  • Drei Punkte ... unmittelbar nach der Variable beim Funktionsaufruf übergeben die Elemente einer Liste einzeln als Argumente.
func main() {
	fmt.Println(adder(1, 2, 3)) // 6
	fmt.Println(adder(9, 9)) // 18

	nums := []int{10, 20, 30}
	fmt.Println(adder(nums...)) // 60
}

func adder(args ...int) int {
	total := 0
	for _, v := range args { // Iteration durch sämtliche Argumente
		total += v
	}
	return total
}

Lambda-Funktionen oder Ternary-Operator? Funktions-Literale! (Anonyme Funktionen als Variablen)

//Functions as values
func main() {
	// Es möglich, Variablen als Funktion zu deklarieren und die Funktion über die Variable aufzurufen:
	var add func(int, int) int
	add = func(a, b int) int { return a + b }
	fmt.Println(add(3, 4))

	// Etwas kürzer mit Typinferenz:
	min := func(a, b int) int {
		if a < b {
			return a
		} else {
			return b
		}
	}
	fmt.Println(min(1,2)) // min(1,2) = 1

	// Aber notwendig ist selbst das nicht. Anonymen Funktionen können die Parameter direkt übergeben werden:
	fmt.Println(func(a, b int) int { if a > b { return a } else { return b }}(3, 4)) // max(3,4) = 4
}

Funktionen mit Gedächtnis: Closures

  • Closure-Funktionen können auf Variablen des Eröffnungskontextes lesend und schreibend zugreifen.
func meineFunktion() func() int{ // Funktion meineFunktion gibt eine anonyme Funktion zurück, die den Typ int zurückgibt.
// Aber bedenke: Der Type ist nicht "int", sondern "func() int", da "func() int" gemeinsam den Rückgabewert bilden! Eindeutigere Schreibweise: "func meineFunktion() (func() int){}"
	outerVar := 2
	meinErgebnis := func() int { return outerVar }
	return meinErgebnis
}
  • Beachte bei Closures, wann und wo die Veränderung stattfindet! Wie bei allen untergeordneten Sprach-Strukturen, werden Variablen zwar außerhalb deklariert, aber innerhalb nicht verändert, sondern kopiert!
package main

import (
	"fmt"
)

func main() {
	a, b := outerFun()
	c := outerFun2()

	fmt.Println(a(), b) // Achtung, Klammer nicht vergessen!
	fmt.Println(a, b)   // ... liefert ansonsten die Adresse von a

	// Entsprechend erfordert der direkte Aufruf der Funktion *zwei* Klammern!
	fmt.Println(outerFun2()()) // => 1
	fmt.Printf("%v %v %v\n", c(), c(), c()) // => 2 3 4

}

func outerFun() (func() int, int) {
	outerVar := 4
	innerFun := func() int {
		outerVar += 10 // Änderung der Variable nur innerhalb der Funktion sichtbar
		return outerVar
	}
	return innerFun, outerVar // => 14, 4
}

var i int // Bei Deklaration außerhalb der Funktion outerFun2 ist i für die anonyme Funktion innen ebenso sichtbar ...

func outerFun2() func() int {
	// ... wie es bei Deklaration innerhalb der Funktion outerFun2 sichtbar wäre:
	//	var i int
	return func() int {
		i++
		return i
	}
}
  • Variablen müssen aber immer zuvor deklariert werden
// Fehler:
func outerFun3() func() int {
	// ??? Hier fehlt die Deklaration. z.B. "var i int" oder "i := 0"
	return func() int {
		i += 1
		return i
	}
}

Speicherverwaltung

Anmerkung
new() ist eine Build-in-Funktion zum Zuweisen von Speicher. new(Typ) gibt einen Zeiger *Typ zurück.
make() ist eine Build-in-Funktion zum Zuweisen von Speicher für Slices, Maps und Channels. make(Typ, Länge, Kapazität) gibt den Wert vom Typ Typ zurück.

Deklarationen

Variablen

var a int // Deklaration ohne Initialisierung
var b int = 42 // Deklaration mit Initialisierung
var b = int(42) // Dasselbe in etwas anderer Schreibweise
var a, b int = 42, 1302 // Deklaration gleicher Typen und Initialisierung über Liste
var MaxInt uint64 = 1<<64 - 1 // Auch die Ergebnisse von Operationen können zugewiesen werden
var c = 42 // Deklaration und Initialisierung durch Typinferenz
c := 42    // Deklaration und Initialisierung durch Typinferenz, alternative Schreibweise

Konstanten

const constant = "Mich kannst Du nie wieder ändern!"
const (
	a = iota // a = 0
	b        // b = 1
	c        // c = 2
)

Zeiger (Pointer)

Speicher-Allozierung durch Pointer auf Variable
Merke
& liefert die Speicher-Adresse einer Variablen
* liefert den Wert, der in einer Speicher-Adresse gespeichert ist
var a int
var b *int // Pointer auf int
a = 10
b = &a // b ist ein Pointer und bekommt die Adresse von a
c := &b
fmt.Println(a)   // 10
fmt.Println(b)   // 0xc420010f50
fmt.Println(c)   // 0xc42000e038
fmt.Println(*b)  // 10
fmt.Println(**c) // 10
**c = 11         // Der Wert an der Adresse, auf die d direkt bzw. c indirekt zeigen, bekommt einen Wert zugewiesen. (Somit ist nun auch a = 11)
fmt.Println(*b)  // 11
fmt.Println(a)   // 11

var d *float64 // Natürlich können Pointer auch mit allen anderen Datentypen deklariert werden
Speicher-Allozierung durch Zuweisung von Speicher an Pointer
// Möglichkeit 1:
// var c *float64 = new(float64)
// Möglichkeit 2:
// var c *float64; c = new(float64)
// Möglichkeit 3:
c := new(float64)
*c = 123.456
fmt.Println(*c)
Zeigerarithmetik (wie in C) gibt es in Go nicht …

… es sei denn, man verwendet das Package unsafe und den Datentyp uintptr:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	i := 123
	var pi *int
	pi = &i

	fmt.Println(pi, *pi)      //0xc42000e310 123
	upi := unsafe.Pointer(pi) // Wandele normalen Pointer in unsafe.Pointer um
	fmt.Println(upi)          //0xc42000e310
	//fmt.Println(*upi)       // Fehler: invalid indirect of upi (type unsafe.Pointer)

	pi2 := (*int)(upi)     // Wandele unsafe.Pointer in normalen Pointer um
	fmt.Println(pi2, *pi2) // 0xc42000e310 123

	// Zeigerarithmetik funktioniert über den Datentyp uintptr
	uipi := uintptr(upi)         // Wandele unsafe.Pointer in uintptr um
	uipi += 1                    // Rechne mit dem Pointer
	upi2 := unsafe.Pointer(uipi) // Wandele uintptr in unsafe.Pointer um

	pi3 := (*int)(upi2)
	fmt.Println(pi3, *pi3) //0xc42000e312 864691128455135232 (Natürlich steht hier jetzt *irgendetwas* ganz anderes an der neuen Speicheradresse!)
}

Typumwandlung

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// Dasselbe mit Typinferenz
j := 42
g := float64(i)
v := uint(f)

// Ebenfalls gültige Schreibweise
w := (int)(u)
x := (float64)(1234)

Arrays, Slices, Strings, Maps, Ranges

Arrays

Deklaration und Initialisierung
var a [10]int // Deklaration eines int-Arrays der Länge 10
a[3] = 42     // Wertezuweisung an das vierte(!) Element des Arrays
i := a[3] 	  // Werte aus Array auslesen
var a [3]int = [3]int{11, 22, 33} // Deklaration und Zuweisung. Wir zählen die Länge des Arrays selber.
var a = [3]int{11, 22, 33} // Gleichbedeutend und etwas kürzer
a := [3]int{11, 22, 33}    // Dasselbe mit Typinferenz
a := [...]int{11, 22, 33}  // Und diesmal zählt der Compiler die Arraylänge für uns
Multidimensionale Arrays
// Mit Deklaration
var multia [2][2]string
multia = [2][2]string{{"α", "β"}, {"γ", "δ"}}
// Mit Typinferenz
multib:=[2][2]string{{"α", "β"}, {"γ", "δ"}}

Slices

Slices sind Arrays sehr ähnlich, ihre Länge ist jedoch nicht festgelegt. Arrays sind unveränderlich, Slices zeigen einen Ausschnitt aus Arrays, besitzen also keinen eigenen Speicherbereich. Deswegen unterscheiden Slices zusätzlich zwischen Länge und Kapazität. Die Länge entspricht der Anzahl der Elemente eines Slices, die Kapazität beschreibt die Anzahl der im Speicher zu reservierenden Elemente des Arrays, auf welches das Slice zeigt.

var a []int // Deklaration eines slice.
var a = []int {1, 2, 3, 4} // Deklaration und Initialisierung ...
a := []int{1, 2, 3, 4} // ... mit Typinferenz
chars := []string{0:"a", 2:"c", 1: "b"} // ["a", "b", "c"]
var b = a[1:4] // Slice von Index 1 bis 3 (Achtung, kein Fehler! 4-1)
var b = a[:3]  // Fehlender low-Index = 0
var b = a[3:]  // Fehlender high-Index = len(a)
var b = a[lo:hi] // Slicegrenzen durch Variablen angezeigt. (Achte auf hi-1!)
Unterschiede zwischen new() and make()
// Erzeugung eines Slices mit new
a = new([10]int)[0:5]

// Erzeugung desselben Slices mit make
a = make([]int, 5, 10) // Erstes Argument = Länge, zweites Argument = Kapazität
a = make([]int, 5)    // Kapazität ist optional

var p *[]int = new([]int)       // Alloziiert einen Slice mit nil als Inhalt und gibt einen Pointer zurück (*p == nil)
fmt.Println(*p) // []
var v  []int = make([]int, 10) // Der Slice referenziert auf ein Array aus 10 ints mit 0 als Inhalt
fmt.Println(v) // [0 0 0 0 0 0 0 0 0 0]
Erzeuge ein Slice aus einem array
x := [3]string{"α","β","γ"}
s := x[:] // Ein slice referenziert auf den Speicherbereich des Arrays x
Kopiere Slice in ein Array
array := [3]string{"a","b","c"}
slice := []string{"x", "y", "z"}
copy(array[:], slice[:])
fmt.Println(array) //[x y z]
Slices sind Zeiger auf Arrays
a := [4]byte{'J', 'a', 'v', 'a'} // Ein Array
s := a[1:] // Ein Slice
s[0] = 'e'
s[1] = 'r'
s[2] = 'k'
fmt.Printf("%s\n", a)
Ein Array, das nicht existiert, wird im Hintergrund erzeugt
var a = []byte{'J', 'a', 'v', 'a'} // Ein Slice, es erzeugt ein anonymes Array im Hintergrund
s := a[1:] // Ein Slice
s[0] = 'e'
s[1] = 'r'
s[2] = 'k'
fmt.Printf("%s\n", a)
Weswegen das Berücksichtigen der Kapazität so wichtig ist!

Ein Slice kann beliebig erweitert werden. Sind jedoch die Grenzen der Kapazität (= die Grenzen des Arrays im Hintergrund) erreicht, wird der Slice im Hintergrund in ein neues Array mit doppelter Kapazität kopiert !

package main

import "fmt"

func main() {
	s := make([]int, 3, 5) // Erstes Argument = Länge, zweites Argument = Kapazität
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s[0] = 10
	s[1] = 21
	s[2] = 32
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s = append(s, 43)
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s = append(s, 54)
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s = append(s, 65) // Hier wird die Kapazität verdoppelt
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s = append(s, 76)
	s = append(s, 87)
	s = append(s, 98)
	s = append(s, 109)
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s = append(s, 120) // Hier wird die Kapazität verdoppelt
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
	s = append(s, 131)
	fmt.Printf("Länge: %v; Kapazität: %v; Inhalt: %v\n", len(s), cap(s), s)
}

führt zu folgendem Ergebnis:

Länge: 3; Kapazität: 5; Inhalt: [0 0 0]
Länge: 3; Kapazität: 5; Inhalt: [10 21 32]
Länge: 4; Kapazität: 5; Inhalt: [10 21 32 43]
Länge: 5; Kapazität: 5; Inhalt: [10 21 32 43 54]
Länge: 6; Kapazität: 10; Inhalt: [10 21 32 43 54 65]
Länge: 10; Kapazität: 10; Inhalt: [10 21 32 43 54 65 76 87 98 109]
Länge: 11; Kapazität: 20; Inhalt: [10 21 32 43 54 65 76 87 98 109 120]
Länge: 12; Kapazität: 20; Inhalt: [10 21 32 43 54 65 76 87 98 109 120 131]

Weitere Details https://blog.golang.org/go-slices-usage-and-internals

Strings

Strings ähneln Arrays, indem ein Slice einen Ausschnitt aus einem String zeigen kann.

fmt.Println("Bye bye Java"[8:10])         // "Ja"
fmt.Println("Bye bye Java"[8])            // 74 bzw. 'J'
fmt.Println(string("Bye bye Java"[8]))    // "J"
fmt.Println([]byte("Bye bye Java"[8:12])) // [[74 97 118 97] bzw. []byte{'J', 'a', 'v', 'a'})

Maps

Sämtliche Types können einer Map zugeordnet werden

package main

import "fmt"

func main() {
	m := make(map[string]int) 
//	m := make(map[string]int,10) // Optional mit Kapazität

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

	delete(m, "Answer") //Element einer Map löschen
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"] // Enthält den Value, ok ist true oder false, falls Element nicht vorhanden
	fmt.Println("The value:", v, "Present?", ok)

	mi := map[string]int{
		"a": 1,
		"b": 2,
		"c": 3,
	}
	fmt.Println(mi)

	pm := &m // Erzeuge einen Pointer auf die Map
	fmt.Println((*pm)["Answer"]) // Achtung! Klammern notwendig …
	// … *pm["Answer"] wäre unzulässig bzw. wäre gleichbedeutend mit *(pm["Answer"])
}

Ranges

Iteration durch die Argumente von Array, Slice oder Map

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)
	}
}

Built-in Types

default: false

bool // (true oder false)

default: ""

string

default: 0

int8 // (-128 bis 127)
int16 // (-32768 bis 32767)
int32 // (-2147483648 bis 2147483647)
int64 // (-9223372036854775808 bis 9223372036854775807)
int // entspricht – von der Systemarchitektur abhängig – int32 oder int64
uint8 // (0 bis 255)
uint16 // (0 bis 65535)
uint32 // (0 bis 4294967295)
uint64 // (0 bis 18446744073709551615)
uint // uint entspricht – von der Systemarchitektur abhängig – uint32 oder uint64
byte // Alias für uint8
rune // Alias für int32, repräsentiert einen Unicode Code Point
uintptr  // unsigned integer, groß genug um das Bitmuster jedes anderen Pointers zu speichern. Siehe https://golang.org/pkg/unsafe/

default: 0.0

float32 // Gleitkommazahlen nach IEEE-754 mit Größe 32 bit (Exponent 8 bit, Mantisse 23 bit)
float64 // Gleitkommazahlen nach IEEE-754 mit Größe 64 bit (Exponent 11 bit, Mantisse 52 bit)

default: (0+0i)

complex64 // Komplexe Zahlen mit float32-Realteil. Eingabe inklusive Imaginärteil i z.B.: (1+2i)
complex128 // Komplexe Zahlen mit float64-Realteil.

default: nil

pointers
functions
interfaces
slices
channels
maps
error // Typ für die Fehlerbehandlung

Strukturen

package main

import "fmt"

type programmingLanguages struct {
	name   string
	rating int
	future bool
}

func main() {

	// Unbenannte Zuweisung in Reihenfolge der Typen
	fmt.Println(programmingLanguages{"php", 4, true})

	// Benannte Zuweisung in beliebiger Reihenfolge
	fmt.Println(programmingLanguages{rating: 2, future: true, name: "Python"})

	// Pointer auf eine Struktur
	fmt.Println(&programmingLanguages{name: "Java", rating: 3, future: false})

	// Unvollständige Zuweisung ist gültig, Lücken erhalten die Default/Null-Werte
	fmt.Println(programmingLanguages{name: "C/C++"})

	// Zugriff auf ein Element der Struktur
	a := programmingLanguages{name: "Go", future: true, rating: 1}
	fmt.Println(a.name)

	// Zeiger ...
	b := &a
	fmt.Println(b.rating) // ... werden automatisch zurückverfolgt, weswegen hier "b.rating" steht und nicht "*b.rating".

	// Strukturen sind veränderlich
	b.name = "Golang"
	fmt.Println(b.name)
	fmt.Println(a) // Zeiger!
}

Operatoren

Arithmetic

+ addition
- subtraction
* multiplication
/ quotient
% remainder
& bitwise and
| bitwise or
^ bitwise xor
&^ bit clear (and not)
<< left shift
>> right shift

Comparison

== equal
!= not equal
< less than
<= less than or equal
> greater than
>= greater than or equal

Logical

&& logical and
|| logical or
! logical not

Other

& address of
* dereference pointer
<- send / receive operator
_ Placeholder, empty identifier

Kontrollstrukturen

Einflußnahme auf den Ablauf der Kontrollstrukturen von innen erfolgt durch die Statements:

return
Verlasse die Funktion sofort
break
Breche die Ausführung der Kontrollstruktur an dieser Stelle ab (in for, switch, select innerhalb der selben Funktion). Sprünge über verschiedene Stufen können mit Labels realisiert werden:
continue
Springe zur nächsten Iteration (in "for" innerhalb der selben Funktion). Sprünge über verschiedene Stufen können mit Labels realisiert werden:

OuterLoop:
	for i = 0; i < n; i++ {
	InnerLoop:
		for j = 0; j < m; j++ {
			switch a[i][j] {
			case nil:
				break OuterLoop
			default:
				continue InnerLoop

fallthrough
Springe zur nächsten case-Bedingung (in switch-Blöcken)

Bedingungen

func main() {
	if x > 0 {
		return x
	} else {
		return -x
	}
	// Vor die Bedingung kann ein Deklarationsstatement gesetzt werden. Im Beispiel bleibt die Variable a nur innerhalb des if-else-Blockes gültig.
	if a := b + c; a < 42 {
		return a
	} else {
		return a - 42
	}
}

Schleifen

  • Es gibt nur ein sehr flexibles for, so daß keine Notwendigkeit für ein while mehr besteht.
for i := 1; i < 10; i++ {
}
var i int
for ; i < 10; { // while - loop
}
var i int
for i < 10 { // die Semikolons können weggelassen werden
}
for { // ohne Bedingung – entspricht einem while (true)
}

Switch

switch m {
case 0, 2, 4, 6:
	tuWas()
default:
	tuNix() // Möglicherweise ungewohnt: Die Position der default-Bedingung ist egal
case 1, 3, 5, 7:
	tuWasAnderes()
}

// Switch ohne Variable
switch {
case a < b:
	tuWas()
	fallthrough // Führe die nachfolgende case-Bedingung auch noch aus!
case a <= b: 
	tuNochWas()
case a > b:
	tuWasAnderes()
case b == d:
	tuNix()
}

// Wie alle Kontrollstrukturen kennt auch switch optionale Variablendeklarationen mit exklusiver Sichtbarkeit
switch os := getMyOS(); os {
	case "windows": fmt.Println("😱")
	case "mac": fmt.Println("😚")
	case "linux": fmt.Println("😊")
	case "bsd": fmt.Println("😎")
	default: fmt.Println("👻")
	}

Sichtbarkeit von Variablen in Kontrollstrukturen

Alle Kontrollstrukturen kennen die optionale Deklaration von Variablen, die nur innerhalb der Kontrollstruktur sichtbar sind

switch i := 123; i { ... }

Ebenfalls gültig:

package main

import (
	"fmt"
)

var i int = 123

func main() {
	switch i := i * 2; i {
	default:
		fmt.Println(i) // Hier ist i gleich 246
	}
	fmt.Println(i) // Ab hier ist i wieder 123
}

Geächtet, aber möglich: goto

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
Label:
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	if i := r.Intn(100); i <= 98 {
		fmt.Print(i)
		goto Label
	} else {
		fmt.Println("Endlich geschafft")
	}
}

Objektorientierung

Go kennt keine Klassen, keine Zugriffsmodifikatoren (public, private, protected) und auch keine explizite Vererbungssyntax.

  • Dafür aber lassen sich Objekteigenschaften (Methoden) auf jeden selbstdefinierten Datentyp übertragen, der Datentyp übernimmt also die Eigenschaften einer Klasse!
  • Die Objektsichtbarkeit wird über die Schreibweise der Identifikatoren auf Modulebene definiert.
  • Und die Vererbung von Eigenschaften erfolgt über die Bildung neuer Verbundtypen aus bestehenden Verbundtypen.

Vererbung

package main

import "fmt"

type pseudoClass struct { // Der Verbundtyp übernimmt die Klassendefinition
	X, Y float64
}

func (this pseudoClass) methode() float64 {
	return this.X + this.Y // "this" ist kein Schlüsselwort in go, die Empfänger-Variable wurde hier nur zur Veranschaulichung so genannt (schlechter Stil!)
}
func (this *pseudoClass) pointerMethode() float64 { // Pointer sind effizienter und sie gestatten Veränderungen der Variable durch Nennung des Speicherbereichs einer Variable anstelle sie zu kopieren
	return this.X - this.Y // "this" ist kein Schlüsselwort in go, die Empfänger-Variable wurde hier nur zur Veranschaulichung so genannt (schlechter Stil!)
}

type pseudoClassD1 struct {
	pseudoClass // Durch die Einbindung des Typs pseudoClass, "erbt" die pseudoClass2 dessen Eigenschaften!
	Z           string
}

type pseudoClassD2 struct {
	pseudoClassD1 // Durch die Einbindung des Typs pseudoClassD1, "erbt" die pseudoClassD2 dessen Eigenschaften (und natürlich auch die von pseudoClass)!
}

func main() {
	instanz0 := pseudoClass{2, 3} // Entspricht Übergabe zweier Parameter an den Konstruktor
	instanz0p := &instanz0        // Zu beachten! Go vollzieht die Konversion zwischen Werten und Pointern bei Methodenaufrufen automatisch
	fmt.Println(instanz0.methode(), "==", instanz0p.methode())
	fmt.Println(instanz0.pointerMethode(), "==", instanz0p.pointerMethode())
	instanz1 := pseudoClassD1{instanz0, "mit irgendeiner Erweiterung"} // Auch Instanzen können übergeben werden
	fmt.Println(instanz1.methode(), instanz1.Z)
	instanz2 := pseudoClassD2{instanz1}         // Übergebe neue Werte für X und Y
	instanz2.X, instanz2.pseudoClassD1.Y = 7, 8 // Überschreiben der Attribute, Zugriff sowohl als Variable der eigenen Instanz als auch über die Abstammung
	fmt.Printf("Berechnungen: 7+8=%v und 7-8=%v. Zwei identische Werte: %v==%v\n", instanz2.methode(), instanz2.pointerMethode(), instanz2.pseudoClassD1.Y, instanz2.Y)
}

Jeder Datentyp wird zum Objekt und kann Methoden erhalten

  • Methoden können auf alle Datentypen angewendet werden, die innerhalb des selben Source-Files deklariert wurden.
package main

import "fmt"

type pseudoClass int // Deklaration eines neuen Datentyps übernimmt die Aufgaben einer "Klasse", hier mit den Eigenschaften des Datentyps "int"

func (self pseudoClass) rechenMethode() int { // "self" zur Bezeichnung der Empfänger-Variable ist ebenfalls schlechter Stil, hilft hier aber beim Verständnis!
	return int(self * self)
}
func (self pseudoClass) rechenMethodeMitArgument(argument int) int {
	return int(int(self) * argument)
}

func main() {
	instanz := pseudoClass(2)                        // Zuweiseung bei Implementierung ähnelt einem "Konstruktor"
	fmt.Println(instanz.rechenMethode())             // 4
	fmt.Println(instanz.rechenMethodeMitArgument(3)) // 6
}

Interfaces

  • Ein Interface ist ein Datentyp.
  • Der Typ Interface ist eine Schablone der Methoden, welche ein Datentyp besitzen muß!
  • Ein Interface wird implementiert, wenn alle geforderten Methoden vorhanden sind.
  • Neuere Implementierungen überschreiben vorherige.
  • Nachfolgend implementiert die Methode os.Stdout das Interface "ReadWriter":
package main

import (
	"fmt"
	"os"
)

type Reader interface {
	Read([]byte) (int, error)
}

type Writer interface {
	Write([]byte) (int, error)
}

type ReadWriter interface {
	Reader
	Writer
}

func main() {
	var w ReadWriter

	w = os.Stdout // Hier wird über das ReadWriter-Interface das Writer-Interface implementiert,
	              // da Read() und Write() als Methoden des Datentyps "File" in Package os
	              // definiert sind und somit die Vorgaben der Interfaces erfüllen.
	              // Erwartet wird das io.Writer-Interface von
	              // func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
	              // (Siehe https://golang.org/pkg/fmt/#Fprintf und https://golang.org/pkg/io/#Writer)
	fmt.Fprintf(w, "Cooler als eine Java ProblemFactory!\n")
}
  • Interface und Implementierung sind voneinander unabhängig. Zum Vergleich – nachfolgender Code würde zu exakt demselben Ergebnis führen, da die Interfaces io.Reader und io.Writer identisch sind mit den vorherigen Interfaces Reader und Writer:
package main

import (
	"fmt"
	"io"
	"os"
)

type ReadWriter interface {
	io.Reader
	io.Writer
}

func main() {
	var w ReadWriter

	w = os.Stdout
	fmt.Fprintf(w, "Cooler als eine Java ProblemFactory!\n")
}

Polymorphie

  • Ein Interface kann mehrere Methoden implemtieren, bzw. gleichen Methodennamen unterschiedliche Gestalt geben. Der Nutzungskontext entscheidet beim Kompilieren über die Auswahl. Da Go (z.B. im Vergleich zu Java oder C++) jedoch keine "Generics" kennt, können keine überladenen Funktionen (Mehrfachdefinition mit unterschiedlichen Parameterlisten) definiert werden.
package main

import "fmt"

type Ungeziefer interface { // Nur eine Konvention: das Interface erhält die Endung "er" hinter dem Methodennamen, wenn das Interface nur eine Methode besitzt.
	Ungezief() string
}

type pseudoClass1 string // Die erste "Klasse", die das Interface implementieren soll

type pseudoClass2 struct { // Die zweite "Klasse", die das Interface implementieren soll
	a string
	b string
}

func (getier pseudoClass1) Ungezief() string {
	return string(getier)
}

func (getier pseudoClass2) Ungezief() string {
	return string(getier.a) + " " + string(getier.b)
}

func (getier pseudoClass2) Gezief() string {
	return string(getier.b) + " aber " + string(getier.a)
}

func main() {
	// Zugriff auf Methoden *der Struktur*
	aa := pseudoClass1("Hallo")
	// gleichbedeutend mit   var aa pseudoClass1 = "Hallo"
	// gleichbedeutend mit   var aa pseudoClass1; aa = "Hallo"
	bb := pseudoClass2{"Hallo", "Welt"}
	fmt.Println(aa.Ungezief()) // "Hallo"
	fmt.Println(bb.Ungezief()) // "Hallo Welt"
	fmt.Println(bb.Gezief())   // "Welt aber Hallo" – zulässig, da Implementierung ohne Interface

	// Zugriff auf Methoden *des Interfaces*
	var cc Ungeziefer
	cc = pseudoClass1("Bye")         // cc implementiert pseudoClass1()
	fmt.Println(cc.Ungezief())       // "Bye"
	cc = pseudoClass2{"Bye", "Java"} // cc implementiert pseudoClass2() und überschreibt die vorherige Implementierung
	fmt.Println(cc.Ungezief())       // "Bye Java"

	// Implementierungen der Methoden der Struktur implementieren das Interface
	cc = aa                    // cc wird von aa überschrieben und implementiert das Interface
	fmt.Println(cc.Ungezief()) // "Hallo"
	cc = bb                    // cc wird von bb überschrieben und implementiert das Interface
	fmt.Println(cc.Ungezief()) // "Hallo Welt"
	//fmt.Println(cc.Gezief()) // Unzulässig, da in der Schablone cc des Interfaces Ungeziefer keine Methode Gezief() definiert ist!

	// Vergleiche Zugriffsmöglichkeiten:
	cc = pseudoClass2{"Borneo", "Java"} // Überschreibe cc mit neuer Implementierung
	fmt.Println(aa.Ungezief())          // "Hallo"
	fmt.Println(bb.Ungezief())          // "Hallo Welt"
	fmt.Println(bb.Gezief())            // "Welt aber Hallo"

	fmt.Println(cc.Ungezief()) // "Borneo Java"
	fmt.Println(aa)            // "Hallo"
	fmt.Println(bb)            // "{Hallo Welt}"
	fmt.Println(cc)            // "{Borneo Java}"
}

Siehe ausführlichen Post zum Thema: http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go

Interface als Universaltyp

  • Genaugenommen sind Variablendeklarationen nichts anderes, als die Implementierung von Interfaces. Betrachte die Ähnlichkeiten:
package main

import "fmt"

type universal interface{}

func main() {
	type integertype int
	type universaltype universal // Dieser Zusatzschritt dient nur der Anschaulichkeit.
	                             // "type universaltype interface{}" würde zum gleichen Ergebnis führen!
	type stringtype string

	var i integertype
	var u universaltype
	var s stringtype

	i, u, s = 0, 0.01, "mache irgendwas mit den Variablen"
	fmt.Println(i, u, s)
}
  • An der Spitze der Hierarchie steht also das Interface selbst. Ohne weitere Einschränkungen direkt davon abgeleitete Datentypen besitzen sämtliche möglichen Eigenschaften. Ein leerer Interfacetyp kann somit zur Definition von Variablen beliebigen Typs verwendet werden ("Unboxing").
package main

import (
	"fmt"
)

/*
type pseudoGenericTyper interface{}
var pseudoGenericVar pseudoGenericTyper
//oder direkt:
*/
var pseudoGenericVar interface{}

func main() {
	pseudoGenericVar = "Object"   // string
	fmt.Println(pseudoGenericVar) //
	pseudoGenericVar = 1          // int
	fmt.Println(pseudoGenericVar) //
	pseudoGenericVar = 1.1        // float
	fmt.Println(pseudoGenericVar) //
}

Type assertions

var t interface{}
t = 123
a := t.(int)
a := t.(float64)

// Type assertion innerhalb eines if-else-Blockes
var b interface{}
b = "Blabla"
if str, ok := b.(string); ok {
	fmt.Println(str)
}

Type switches

package main

import (
	"fmt"
)

var t interface{}

func main() {
	a := 123 // Experimentiere auch mal mit 123.456 oder "123" ...
	t = &a   // ... und lasse das & weg

	switch t := t.(type) {
	default:
		fmt.Printf("unexpected type %T\n", t)
	case bool:
		fmt.Printf("boolean %t\n", t)
	case int:
		fmt.Printf("integer %d\n", t)
	case float64:
		fmt.Printf("float64 %g\n", t)
	case *bool:
		fmt.Printf("pointer to boolean %t\n", *t)
	case *int:
		fmt.Printf("pointer to integer %d\n", *t)
	case *float64:
		fmt.Printf("pointer to float64 %g\n", *t)
	case string:
		fmt.Printf("string %s\n", t)
	}
}

Composite Types mittels Interface und Struct

package main

import (
	"fmt"
	"strconv"
)

type universaltype interface{} // Ein leeres Interface als Universaltyp

type Number interface {
	String() string    // Eine Methode des Interfaces fmt.Stringer wird nun auch zur Methode des Interfaces Number
	latin(bool) string // Eine Methode des Interfaces Number
}

type Numb struct {
	universal universaltype
}

type NumbOhne struct {
	universal universaltype
}

//Mit dieser Methode implementiert Number das Interface fmt.Stringer, benötigt von fmt.Println, fmt.Printf etc.
func (n Numb) String() string {

	switch t := n.universal.(type) {
	default:
		return fmt.Sprintf("Unzulässiger Typ: %T", t)
	case int:
		return strconv.Itoa(t) + n.latin(false)
	case string:
		return t + n.latin(true)
	}
}

func (n NumbOhne) String() string {

	switch t := n.universal.(type) {
	default:
		return fmt.Sprintf("Unzulässiger Typ: %T", t)
	case int:
		return strconv.Itoa(t)
	case string:
		return t
	}
}

func (n Numb) latin(romanOnOff bool) string {
	if romanOnOff == true {
		return " (Römische Zahl)"
	} else {
		return " (Arabische Zahl)"
	}

}

func main() {
	var nr1a, nr1b Number // nr1a und nr1b imeplementieren das Interface.
	nr1a = Numb{"LXXXVIII"}
	nr1b = Numb{88}
	fmt.Printf("%s = %s\n", nr1a, nr1b) // LXXXVIII (Römische Zahl) = 88 (Arabische Zahl)
	// Alle übrigen Aufrufe der Methode erfolgen direkt bzw. ohne die Notwendigkeit, das Interface zu implementieren!
	var nr2a = Numb{"LXXXIX"}
	var nr2b = NumbOhne{89}
	fmt.Printf("%s = %s\n", nr2a, nr2b)                   // LXXXIX (Römische Zahl) = 89 (Arabische Zahl)
	fmt.Printf("%s = %s\n", NumbOhne{"XC"}, NumbOhne{90}) // XC (Römische Zahl) = 90 (Arabische Zahl)
	fmt.Println(Numb{123.456})                            // Unzulässiger Typ: float64
}

Error-Handling

  • Go ist beim Werfen von Fehlern nicht auf try-catch-finally Blöcke und Exceptions angewiesen, sondern nutzt hierzu den Datentyp error. Nachfolgend drei Beispiele für das Werfen von Fehlern:
package main

import (
	"errors"
	"fmt"
	"time"
)

type ErrorMessage1 struct {
	Zeitpunkt time.Time
	Meldung   string
}

func (e *ErrorMessage1) Error() string { // Implementiere die Funktion Error() des errors-Interfaces als Methode der Struktur ErrorMessage1
	return fmt.Sprintf("Um %v, %s", e.Zeitpunkt, e.Meldung)
}

func doFirstError() error {
	return &ErrorMessage1{time.Now(), "Ach manno!"}
}

type ErrorMessage2 string

func (e ErrorMessage2) Error() string {
	return string(e)
}

func doSecondError() error {
	return ErrorMessage2("Na sowas!")
}

func doThirdError() error {
	return errors.New("Grrr!")
}

func main() {
	if err := doFirstError(); err != nil {
		fmt.Println(doFirstError()) // Um 2016-12-31 23:59:59.999999999 +0100 CET, Ach manno!
	}
	if err := doSecondError(); err != nil {
		fmt.Println(err) // Na sowas!
	}
	if err := doThirdError(); err != nil {
		fmt.Println(err) // Grrr!
	}
}

Goroutinen (Nebenläufigkeit)

Asynchrone Laufzeit

Goroutinen werden im selben Adressraum ausgeführt, der Zugriff auf gemeinsamen Speicher muss also synchronisiert werden. Es ist im Hauptprogramm zu beachten, auf nebenläufige Unterprogramme zu warten.

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func say(s string, proc int) {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < 5; i++ {
		time.Sleep(time.Duration(r.Intn(10000)) * time.Millisecond) // Der Zufallsgenerator macht die unterschiedliche Ausführungsdauer der einzelnen Unterprogramme noch besser sichtbar.
		fmt.Printf("Prozess=%v, Schleife=%v, Wert=%v\n", proc, i, s)
	}
	fmt.Printf("\nProzess %v ist fertig!\n\n", proc)
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Normalerweise laufen alle Prozesse im gleichen Thread. Sage dem Compiler, wieviele Threads er öffnen darf.

	go say("Bye", 1) // Wird mit dem Schlüsselwort go eine Routine ausgeführt, fährt das Hauptprogramm ohne zu warten fort!
	go say(" ", 2)
	go say("bye", 3)
	go say(", ", 4)
	go say("Java", 5)
	go say("!", 6)

	// Die Funktion ist nun 6 mal gestartet worden. Und unser Programm ist fertig - fertig, ohne daß auch nur eine einzige say-Funktion hätte ausgeführt werden können. Deswegen verordnen wir unserem Hauptprogramm etwas Wartezeit:
	time.Sleep(time.Second * 60)
	fmt.Println("Puuuh!")
}

Kommunikation zwischen Routinen

  • Die Kommunikation zwischen den Routinen läuft über Channels. Die Channel sind (wie Variablen) typisiert. Ein Channel kann als Pipe oder Semaphore verstanden werden.
  • Channel-Typen:
chan   // bidirektional
chan<- // nur senden
<-chan // nur empfangen
  • Channel-Deklaration:
ch <- v              // Sende v über Kanal ch.
v := <-ch            // Empfange von ch und weise den empfangenen Wert v zu.
ch := make(chan int) // Der Channel wird in der Regel im Hauptprogramm definiert und wird wie eine Variable an die Unterprogramme übergeben.
ch := make(chan int, 10) // Gepufferter Channel, der 10 Werte zwischenspeichern kann.
Anmerkung zu Channel-Puffern
Ist der Channel ungepuffert oder die maximale Anzahl der zu puffernden Variablen erreicht, dann
* wird der Schreiber solange blockiert, bis der Channel ausgelesen wurde und wieder leer ist!
* wird der Leser solange blockiert, bis der Channel nicht mehr leer ist und wieder einen Inhalt hat!
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func think(s string, proc int, c chan string) {
	var timestamp [2]int64
	timestamp[0] = time.Now().Unix()
	for i := 0; i < 5; i++ {
		rand.Seed(time.Now().UnixNano())
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)      // Der Zufallsgenerator macht die unterschiedliche Ausführungsdauer der einzelnen Unterprogramme noch besser sichtbar.
		c <- fmt.Sprintf("Prozess=%v, Schleife=%v, Wert=%v\n", proc, i, s) // Schreibe in Channel
	}
	timestamp[1] = time.Now().Unix()
	c <- fmt.Sprintf("\nProzess %v ist fertig und hat %v Sekunden gedauert!\n\n", proc, (timestamp[1] - timestamp[0])) // Schreibe in Channel
}

func say(l int, c chan string) {
	for i := 1; i <= l; i++ {
		cvalue, cstatus := <-c
		if cstatus {
			fmt.Println(cvalue) // Lese aus Channel
		} else {
			break
		}
	}
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Normalerweise laufen alle Prozesse im gleichen Thread. Sage dem Compiler, wieviele Threads er öffnen darf.
	ChanelNo5 := make(chan string, 1)    // Gepufferte Ausgabe wäre hier nicht nötig, ist aber möglich

	go think("Bye", 1, ChanelNo5) // Die Channel werden wie Variablen an die Unterprogramme übergeben
	go think(" ", 2, ChanelNo5)
	go think("bye", 3, ChanelNo5)
	go think(", ", 4, ChanelNo5)
	go think("Java", 5, ChanelNo5)
	go think("!", 6, ChanelNo5)
	say((6*5 + 6), ChanelNo5)
	close(ChanelNo5) // Channels können wieder geschlossen werden, wenn sie nicht mehr benötigt werden
	fmt.Println("Puuuh!")
}

Das selektive Warten auf bestimmte Channel-Aktivitäten

Ein der switch-Kontrollstruktur sehr ähnlicher select-Block ermöglicht die Bedienung mehrerer Channels. Priorität hat der Channel, der (zum Lesen/Schreiben gleichermaßen) bereit ist. Warten mehrere Channels, erfolgt die Priorisierung zufällig. Es gibt auch eine default-Bedingung, die dann ausgeführt wird, wenn kein Channel bereit ist. Beim Weglassen der default-Bedingung, blockiert select solange, bis ein Channel bereit ist.

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func think(s string, proc int, c chan string, stop chan bool) {
	var timestamp [2]int64
	timestamp[0] = time.Now().Unix()
	for i := 0; i < 5; i++ {
		rand.Seed(time.Now().UnixNano())
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
		c <- fmt.Sprintf("Prozess=%v, Schleife=%v, Wert=%v\n", proc, i, s) // Schreibe in Channel
	}
	timestamp[1] = time.Now().Unix()
	c <- fmt.Sprintf("\nProzess %v ist fertig und hat %v Sekunden gedauert!\n\n", proc, (timestamp[1] - timestamp[0])) // Schreibe in Channel, ...
	time.Sleep(time.Millisecond * 10)                                                                                  // ... warte etwas, damit die letzte Message noch sicher ankommt ...
	stop <- true                                                                                                       // ... und beende auch alle anderen Routinen
}

func say(l int, c chan string, stop chan bool) {
	for i := 1; i <= l; i++ {
		select {
		case cvalue, cstatus := <-c:
			if cstatus {
				fmt.Println(cvalue) // Lese aus Channel
			} else {
				stop <- true // Wenn was nicht stimmen sollte, sende dasselbe Signal zum Abbruch, wie in Methode think
			}
		case <-stop:
			return
		default:
			time.Sleep(50 * time.Millisecond)
		}
	}
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ChanelNo5 := make(chan string, 1)
	ChanelNo19 := make(chan bool) // Sobald dieser Channel true enthält, sollen alle Prozesse beendet werden.

	go think("Bye", 1, ChanelNo5, ChanelNo19)
	go think(" ", 2, ChanelNo5, ChanelNo19)
	go think("bye", 3, ChanelNo5, ChanelNo19)
	go think(", ", 4, ChanelNo5, ChanelNo19)
	go think("Java", 5, ChanelNo5, ChanelNo19)
	go think("!", 6, ChanelNo5, ChanelNo19)
	say((6*5 + 6), ChanelNo5, ChanelNo19)
	close(ChanelNo5) // Channels können wieder geschlossen werden, wenn sie nicht mehr benötigt werden
	close(ChanelNo19)
	fmt.Println("Puuuh!")
}

go test

Konvention bei Datei- und Funktionsnamen

Referenz-Code

  • z.B. in Datei main.go
package main

import (
	"fmt"
	"math"
)

var input float64

func main() {

	fmt.Println("Enter number:")
	fmt.Scan(&input)
	fmt.Printf("math: √%f=%.10f\n", input, math.Sqrt(input))
	fmt.Printf("homemade: √%f=%.10f\n", input, Sqrt(input))
}

func Sqrt(radikand float64) float64 {
	ergebnis := float64(2)
	ergebnisAlt := float64(0)
	for {
		ergebnis = ergebnis - (ergebnis*ergebnis-radikand)/(2*ergebnis)
		// Iteriere solange, bis vorherigeZahl und aktuelleZahl bis auf 10 Stellen hinter dem Komma gleich sind
		if int(ergebnis*1E10) == int(ergebnisAlt*1E10) {
			break
		}
		ergebnisAlt = ergebnis
	}
	return ergebnis
}

Testcode

  • z.B. in Datei main_test.go oder mein_test.go oder Dein_test.go; (Nicht jedoch maintest.go!)
package main

import (
	"math"
	"testing"
)

//Namenskonvention: Test…
func TestMyTestingFunction(t *testing.T) {
	var testzahl float64 = 12361423

	ergebnisIst := Sqrt(testzahl)
	ergebnisSoll := math.Sqrt(testzahl)

	if ergebnisIst != ergebnisSoll {
		t.Error("Erwartung:", ergebnisSoll, "Ergebnis:", ergebnisIst)
	}
}

//Namenskonvention: Benchmark…
func BenchmarkMyBenchmarkingFunctionHomemade(b *testing.B) {
	var testzahl float64 = 12361423
	for i := 0; i < b.N; i++ {
		_ = Sqrt(testzahl)
	}
}

//Namenskonvention: Benchmark…
func BenchmarkMyBenchmarkingFunctionMath(b *testing.B) {
	var testzahl float64 = 12361423
	for i := 0; i < b.N; i++ {
		_ = math.Sqrt(testzahl)
	}
}

Ausführung

Unittests

$ go test -v
=== RUN   TestMyTestingFunction
--- PASS: TestMyTestingFunction (0.00s)
PASS
ok  	mySqrt	0.008s

Benchmarks

$ go test -test.bench=.*
BenchmarkMyBenchmarkingFunctionHomemade-4   	10000000	       121 ns/op
BenchmarkMyBenchmarkingFunctionMath-4       	2000000000	         0.31 ns/op
PASS
ok  	mySqrt	1.996s

Profiling

$ go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
$ go tool pprof cpu.prof
Entering interactive mode (type "help" for commands)
(pprof) png > Schaubild.png
(pprof) weblist
(pprof) help
Hinweis: Beeinflussung der Speicherverwaltung
  • GC findet nur im dynamischen Speicher (Heap) statt.
  • Arrays (nicht Slices!) stehen im Stapelspeicher (Stack) und können zur Vermeidung von GCs genutzt werden.
  • Die Zuweisung von nil (a = nil) gibt den Speicherbereich für die GC frei; durch die "Weiternutzung" des Speicherbereichs (a = a[:0]) kann die GC überlistet werden.
  • Die Garbage Collection wird ausgelöst, sobald der Anteil frisch zugewiesener Daten im Verhältnis zu den nach der vorhergehenden GC verbliebenden Daten einen bestimmten Prozentsatz erreicht hat:

import (
	"runtime"
	"runtime/debug"
)

func main() {
	
	debug.SetGCPercent(50) // Sobald der Anteil neuer Daten im Heap (seit der letzten GC) 50% des Anteils der Altdaten erreicht hat, wird die GC ausgelöst
	debug.SetGCPercent(100) // Der Default-Wert liegt bei 100%
	debug.SetGCPercent(-1) // Die GC wird vollständig abgeschaltet!
	runtime.GC() // Die GC wird manuell ausgelöst
	
}

Siehe http://stackoverflow.com/questions/12277426/how-to-minimize-the-garbage-collection-in-go

C code einbinden

Benutzung von cgo über das Pseudo-Package "C"

Compiler- und linker-flags (CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS, DFLAGS, …)
sind notwendig und werden als Kommentar-Annotationen mit #cgo-Direktive direkt über dem Import von "C" bekannt gemacht.
package main

import (
	"fmt"
	Go "math"
	"time"
)

// #cgo CFLAGS: -O2 -march=native
// #cgo LDFLAGS: -lm
// #include <math.h>
import "C"

const steps int64 = 100000000

var a float64
var b C.double

func main() {
	// Verwende math aus den Go-Packages
	t0 := time.Now().UnixNano()
	for i := (int64)(1); i <= steps; i++ {
		a = Go.Sqrt(float64(i))
	}
	t1 := time.Now().UnixNano()
	fmt.Println(float64(t1-t0) / float64(steps)) // Durchschnittliche Ausführungsdauer in Nanosekunden

	// Verwende math aus den C-Packages
	t2 := time.Now().UnixNano()
	for i := (C.int)(1); i <= C.int(steps); i++ {
		b = C.sqrt(C.double(i))
	}
	t3 := time.Now().UnixNano()
	fmt.Println(float64(t3-t2) / float64(steps)) // Durchschnittliche Ausführungsdauer in Nanosekunden
}

Siehe https://golang.org/cmd/cgo/

IDE

  • Leicht aber erstaunlich mächtig und ausgereift: liteIDE

Links

Benchmark

Siehe Benchmark Go vs. Java vs. Python[4]

Anmerkungen

  1. Dieser und andere Aspekte werden sehr anschaulich erklärt von Peter Verhas: "Comparing Golang with Java", JavaDeep, April 27, 2016
    Unbedingt lesenswert ist hierzu auch die sehr objektive kritische Analyse von Daniel Lemire: "Best programming language for high performance (January 2017)?", Daniel Lemire's blog, January 16, 2017
  2. Einen bemerkenswerten Trend zu Go erkennt auch das Java-Infoportal Jaxenter in: Dominik Mohilo: "Google Go: Darum ist die Programmiersprache so beliebt", 9. März 2017
  3. https://golang.org/doc/install/gccgo: "[...] The GCC 4.9 releases include a complete Go 1.2 implementation. The GCC 5 releases include a complete implementation of the Go 1.4 user libraries. The Go 1.4 runtime is not fully merged, but that should not be visible to Go programs. [...]"
  4. Auf Basis von BenchmarkPzf.go, BenchmarkPzf.py und BenchmarkPzf.java