Der Golang-Spicker

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

Die Programmiersprache Go (oder "Golang", für die Suchmaschinen)

Warum Go? Achtung - subjektiv!

Das offizielle Maskottchen – der Go-Gopher von Renee French

Bei Betrachtung vier populärer Rankings für Programmiersprachen (Tiobe-Index, PyPl-Index, IEEE Spectrum Ranking 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 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 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]

Inhaltsverzeichnis

Ein Go-Spickzettel (zu deutsch "Cheat Sheet")

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)

Streuung von Dateien

  • Wenn innerhalb eines Verzeichnisses unterschiedliche Dateien mit der Endung .go, dem gleichen package zugeordnet sind, werden sie vom Compiler als eine zusammenhängende Datei interpretiert. ⇒ Auf diese Weise lassen sich z.B. Funktionen sehr einfach in getrennte Dateien auslagern.

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-Operation? 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
}

Sichtbarkeit von Variablen in anonymen Funktionen

package main

import (
	"fmt"
)

func altera(a int) {
	a = 2
	fmt.Println(a)
}

func main() {
	a := 1
	fmt.Println(a) // Vorgabe 1

	// Übergabe an benannte Funktion
	altera(a)      // geändert zu 2
	fmt.Println(a) // wieder 1

	// Anonyme Funktion ohne Übergabe
	func() {
		a = 3
		fmt.Println(a) // geändert zu 3
	}()
	fmt.Println(a) // bleibt 3 !!!

	// Übergabe an anonyme Funktion
	func(a int) {
		a = 4
		fmt.Println(a) // geändert zu 4
	}(a)
	fmt.Println(a) // wieder 3
}

Funktionen mit Gedächtnis: Closures

  • Closure-Funktionen können auf Variablen des Eröffnungskontextes lesend und schreibend zugreifen.
package main

import (
	"fmt"
)

func main() {
	cf := meineFunktion()
	fmt.Println(cf()) //1
	fmt.Println(cf()) //2
	fmt.Println(cf()) //3
}

func meineFunktion() func() int { // Funktion meineFunktion gibt eine anonyme Funktion zurück, die den Typ int zurückgibt.
	// Aber bedenke: Der Typ ist nicht "int", sondern "func() int", da "func() int" gemeinsam den Rückgabewert bilden!
	// Eindeutigere Schreibweise: "func meineFunktion() (func() int){}"
	outerVar := 0
	meinErgebnis := func() int {
		outerVar++
		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.

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 Abschnitt Zeigerarithmetik

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

Literale

Zeichenketten (string literals)

fmt.Println("Strings benötigen einen Backslash als \"Escape-Code\" und kennen Unicode-Points wie \u039B bzw. \U0000039B und Escape-Characters wie Tab\toder Newline\n")
fmt.Println(`Raw-Strings enthalten auch "Anführungszeichen', kennen aber keine Unicode-Points wie \u039B und kein Newline\n`)

Einzelne Zeichen (rune literals)

fmt.Println(string('H'), 'a', string('l'), 'l', 'o') //H 97 l 108 111

Ganzzahlen (integer literals)

fmt.Println(255, 0xff, 0377) // Jeweils die Zahl 255 in Dezimal-, Hexadezimal- und Oktal-Schreibweise

Fließkommazahlen (floating point literals)

fmt.Println(1.234, 1.234e+0, 1.23e-4, .123, .123456E+5) //1.234 1.234 0.000123 0.123 12345.6

Imaginäre Zahlen (imaginary literals)

fmt.Println(0i, 11i, 1.234i, 1.234e+0i, 1.23e-4i, 1E2i, .123i, .123E+4i) //(0+0i) (0+11i) (0+1.234i) (0+1.234i) (0+0.000123i) (0+100i) (0+0.123i) (0+1230i)

Deklarationen

Variablen

var a int // Deklaration ohne Initialisierung
var b int = 42 // Deklaration mit Initialisierung
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         // dasselbe, alternative Schreibweise
var c = int(42) // dasselbe mit Typumwandlung zur Typbestimmung

Konstanten

// irgendwasGoiges project main.go
package main

const (
	constant          = "Die Deklaration des Typs ist eigentlich unnötig ..."
	constanter string = "... die Initialisierung erfolgt zwangsläufig durch Typinferenz, da der Wert bekannt ist."
)

const (
	a = iota        // a = 0 // Die erste iota-Zuweisung ist notwendig, ...
	b = iota        // b = 1 // ... alle weiteren sind optional, ...
	c               // c = 2 // ... es wird innerhalb der const()-Deklaration trotzdem weiter gezählt
	d int    = iota // d = 3
)

const e = iota // e = 0

func main() {
	println(a, b, c, d, e) //0 1 2 3 0
}
Anmerkung
Automatische Aufzählungen (Enumerationen) lassen sich über die Zuweisung des Schlüsselwortes iota an die Konstante umsetzen.

Typ-Deklaration

  • Ein deklarierter Typ ist mit dem ableitenden Typ nicht identisch!
package main

import "fmt"

type unsigned_short uint8

func main() {
	var a uint8
	var b byte
	var c unsigned_short

	a = 12
	b = a	
	c = 12 // "c = a" oder "c = b" ist nicht erlaubt

	fmt.Println(a + uint8(b) + (uint8)(c)) // type casting ist notwendig
}
Beispiel für die praktische Anwendung: Ersatz für den enum-Datentyp (C++, Java u.a.)
package main

import "fmt"

type (
	sheepType string // To ensure, that the set of constants is limited (a kind of enumeration)
	dogType   string
)

const (
	SHEEP1 sheepType = "Shaun"
	SHEEP2 sheepType = "Charly"
	SHEEP3 sheepType = "Timmy"
	DOG    dogType   = "Bitzer"
	PIG    string    = "Oink"
)

func doSomethingWithSheepsOnly(s sheepType) {
	fmt.Println(s)
}

func main() {
	doSomethingWithSheepsOnly(SHEEP1)
	doSomethingWithSheepsOnly(SHEEP2)
	doSomethingWithSheepsOnly(SHEEP3)
	doSomethingWithSheepsOnly(DOG) // ... cannot use DOG (type dogType) as type sheepType in argument to doSomethingWithSheepsOnly
	doSomethingWithSheepsOnly(PIG) // ... cannot use PIG (type string) as type sheepType in argument to doSomethingWithSheepsOnly
}

Typ-Alias

  • Ein Alias-Typ ist mit dem ableitenden Typ identisch!
  • Ein Type-Alias erleichtert das Refactoring, da die Änderung der Definition sämtliche Vorkommen in Code einschließt. Sehr bequem kann somit z.B. aus int64 int32 oder aus int64 float64 gemacht werden, ohne diese Änderung manuell im gesamten Code durchführen zu müssen.
package main

import "fmt"

type unsigned_short = uint8

func main() {
	var a uint8
	var b byte
	var c unsigned_short

	a = 12
	b = a
	c = b

	fmt.Println(a + b + c) // valide, denn Alias-Typen benötigen kein type casting
}

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, aber natürlich können Pointer auch für alle anderen Datentypen deklariert werden
a = 10
b = &a             // b ist ein Pointer (Datentyp *int) und bekommt die Adresse von a
fmt.Println(a)     //10
fmt.Println(b)     //0xc42000e310
fmt.Println(*b)    //10
fmt.Println(&*b)   //0xc42000e310
fmt.Println(*&*b)  //10
c := &b            // c hat den Datentyp **int
fmt.Println(c)     //0xc42000c028
fmt.Println(&c)    //0xc42000c038
fmt.Println(**c)   //10
fmt.Println(*&c)   //0xc42000c028
fmt.Println(&*&c)  //0xc42000c038
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

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 eigentlich nicht …

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

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	i := [3]int16{123, 456, 789}
	var pi *[3]int16
	pi = &i

	fmt.Println(pi, *pi)      //&[123 456 789] [123 456 789]
	upi := unsafe.Pointer(pi) // Wandele normalen Pointer in unsafe.Pointer um
	fmt.Println(upi)          //0xc42000e2f2
	//fmt.Println(*upi)       // Fehler: invalid indirect of upj (type unsafe.Pointer)

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

	uipi := uintptr(upi)         // Wandele unsafe.Pointer in uintptr um
	uipi += 2                    // Rechne mit dem Pointer (Bei int16: +2 Bytes)
	upi2 := unsafe.Pointer(uipi) // Wandele uintptr in unsafe.Pointer um ...
	pi3 := (*int16)(upi2)        // ... und danach in einen normalen Pointer

	fmt.Println(pi3, *pi3)                                                   //0xc42000e2f4 456
	fmt.Println(unsafe.Pointer(uipi+4), *(*int16)(unsafe.Pointer(uipi + 2))) //0xc42000e2f8 789

	println(uintptr(unsafe.Sizeof(i)))  //6 (Ermittle die Größe der gesamten Variable in Bytes)
	println(uintptr(unsafe.Alignof(i))) //2 (Pointer Alignment/Speicherausrichtung in Abhängigkeit vom Typ, hier sozusagen die "Breite" eines einzelnen Array-Elements in Bytes; z.B. 2 bei int16)

	type struktur struct {
		a int8
		b string
	}
	j := struktur{123, "abc"}

	println(uintptr(unsafe.Offsetof(j.a))) //0 (Nur bei Strukturen: Offset eines Elements in Relation zur Basis
	println(uintptr(unsafe.Offsetof(j.b))) //8
	println(uintptr(unsafe.Alignof(j.a)))  //1 (1 Byte bei int8)
	println(uintptr(unsafe.Alignof(j.b)))  //8
	println(uintptr(unsafe.Alignof(j)))    //8
	// Beachte, daß der tatsächliche Platzbedarf von der Speicherausrichtung abweichen kann!
	println(uintptr(unsafe.Sizeof(j.a))) //1
	println(uintptr(unsafe.Sizeof(j.b))) //16
	println(uintptr(unsafe.Sizeof(j)))   //24
}

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 - funktioniert aber nur mit Typinferenz!
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]

var m [][]string = make([][]string,10) // Auch multidimensionale Slices können alloziiert werden
Erzeuge ein Slice aus einem array
x := [3]string{"α","β","γ"}
s := x[:] // Ein slice referenziert auf den Speicherbereich des Arrays x
Kopiere Slice mit copy()
array := [3]string{"a","b","c"}
slice := []string{"x", "y", "z"}
copy(array[:], slice[:]) // Hier wird ein Slice in ein Array kopiert
fmt.Println(array) //[x y z]
Erweitere ein Slice mit append()
slice := []string{"a", "b", "c"}
slice = append(slice, "d") // Das Slice erhält ein zusätzliches Element
fmt.Println(slice)         //[a b c d]
anotherSlice := []string{"e", "f", "g"}
slice = append(slice, anotherSlice...) // Die drei Punkte ... der "Variadic Functions"
// expandieren einen Slice in seine einzelnen Elemente und dienen so zur
// Aneinanderreihung zweier Slices
fmt.Println(slice) //[a b c d e f g]
Slices sind eigentlich nichts anderes als Zeiger auf Arrays
  • Ein Array, das nicht existiert, wird im Hintergrund erzeugt.
Vergleiche Array ...
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)
... mit Slice
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. (Und kopieren ist "teuer", d.h. Kopieren verbraucht Speicherplatz und Rechenleistung!)

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. Allerdings ist zu bedenken, daß Sonderzeichen (außerhalb der ASCII-Tabelle) in einem String mehr als ein Byte beanspruchen können!

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	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'})
	fmt.Println(string("ABC"[0:3]))           //ABC
	fmt.Println(string("ÄÖÜ"[0:3]))           //Ä�
	fmt.Println(string("ÄÖÜ"[0:4]))           //ÄÖ
	fmt.Println(string("ÄÖ語"[0:6]))           //ÄÖ��
	fmt.Println(string("ÄÖ語"[0:7]))           //ÄÖ語
	fmt.Println(len("ÄÖ語"),utf8.RuneCountInString("ÄÖ語")) //7 3
	for _, c := range "ÄÖ語" {
		fmt.Println(string(c), rune(c)) // Ä 196, Ö 214, 語 35486
	}
}

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"])
}
  • … auch Funktionen!
package main

import "fmt"

func main() {
	mf := make(map[int]func(float64, float64) float64)

	mf[1] = add
	mf[2] = mult
	mf[3] = sub
	mf[4] = div

	for i := 1; i <= 4; i++ {
		fmt.Println(mf[i](2, 3)) //5 ⇒ 6 ⇒ -1 ⇒ 0.6666666666666666
	}
}

func add(a float64, b float64) float64 {
	return a + b
}

func mult(a float64, b float64) float64 {
	return a * b
}

func sub(a float64, b float64) float64 {
	return a - b
}

func div(a float64, b float64) float64 {
	return a / b
}

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

Strukturen

Definition und Zugriff

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}) //{php 4 true}

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

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

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

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

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

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

Tags

  • Struktur-Elemente können zusätzliche Tags erhalten und über das Package reflect genutzt werden. (Sinnvoll einzusetzen z.B. beim OR-Mapping)
package main

import (
	"fmt"
	"reflect"
)

type programmingLanguages struct {
	name   string `json:"fieldName"`
	rating int    `xml:"rating"`
	future bool   `abc:""`
}

func main() {
	a := programmingLanguages{name: "Go", future: true, rating: 1}
	ra := reflect.TypeOf(a)

	fmt.Println(ra)                             //main.programmingLanguages
	fmt.Println(ra.Field(0))                    //{name main string json:"fieldName" 0 [0] false}
	fmt.Println(ra.Field(0).Tag.Get("json"))    //fieldName
	fmt.Println(ra.Field(0).Tag.Lookup("json")) //fieldName true
	fmt.Println(ra.Field(2).Tag.Lookup("abc"))  // true
}

Operatoren

Arithmetik

+ Addition
- Subtraktion
* Multiplikation
/ Division
% Rest einer Division ("Modulo")
& bitweise UND (6 & 3 = 2, da 00000110 AND 00000011 = 00000010)
| bitweise ODER (6 | 3 = 7, da  00000110 OR 00000011} = 00000111)
^ bitweise EXKLUSIVES ODER  (6 ^ 3 = 5, da  000000110 XOR 00000011 = 00000101)
&^ bitweise UND NICHT (6 &^ 3 = 4, da  00000110 NAND 00000011 = 000000100)
<< bitweise Verschiebung nach links (left shift) (6<<3 = 48, da  00000110 um drei Stellen nach links verschoben = 00110000)
>>  bitweise Verschiebung nach rechts (right shift) (6>>2 = 1, da  00000110 um zwei Stellen nach rechts verschoben = 00000001)

Vergleiche

== gleich
!= ungleich
< kleiner 
<= kleiner oder gleich
> größer
>= größer oder gleich

Logische Verknüpfungen

&& logisches UND
|| logisches ODER
!  logisches Negation

Besonderheiten

& Speicheradresse von
* Zeiger auf Speicheradresse
<- Schreibe in/Lese aus Channel
_ Platzhalter, leer

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 keineKlasse struct { // Der Verbundtyp übernimmt die Klassendefinition
	X, Y float64
}

func (this keineKlasse) 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 *keineKlasse) 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 keineKlasseD1 struct {
	keineKlasse // Durch die Einbindung des Typs keineKlasse, "erbt" die keineKlasse2 dessen Eigenschaften!
	Z           string
}

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

func main() {
	instanz0 := keineKlasse{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())                                                                                                          // 5 == 5
	fmt.Println(instanz0.pointerMethode(), "==", instanz0p.pointerMethode())                                                                                            //  -1 == -1
	instanz1 := keineKlasseD1{instanz0, "mit irgendeiner Erweiterung"}                                                                                                  // Auch Instanzen können übergeben werden
	fmt.Println(instanz1.methode(), instanz1.Z)                                                                                                                         // 5 mit irgendeiner Erweiterung
	instanz2 := keineKlasseD2{instanz1}                                                                                                                                 // Übergebe neue Werte für X und Y
	instanz2.X, instanz2.keineKlasseD1.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.keineKlasseD1.Y, instanz2.Y) // Berechnungen: 7+8=15 und 7-8=-1. Zwei identische Werte: 8==8
}


Wo Pointer notwendig sind

  • Da Variablen in untergeordneten Kontrollstrukturen und Funktionen/Methoden zwar global lesbar sind aber nur innerhalb des eigenen Gültigkeitsbereichs beschreibbar, ist eine Methode über den Zeiger auf einen Strukturtyp zu implementieren, wenn innerhalb der Methode eine Veränderung durchgeführt wird, die auch für nachfolgende Methoden-Zugriffe auf den Strukturtyp erhalten bleiben soll.
package main

import "fmt"

type keineKlasse struct {
	X int
}

func (this keineKlasse) setze() {
	this.X = 1
}
func (this *keineKlasse) psetze() {
	this.X = 2
}

func (this keineKlasse) lese() int {
	return this.X
}

func (this *keineKlasse) plese() int {
	return this.X
}

func main() {
	var a keineKlasse = keineKlasse{}
	a.setze()
	fmt.Println(a.lese())  // 0 und nicht etwa 1
	fmt.Println(a.plese()) // 0 und nicht etwa 1
	a.psetze()
	fmt.Println(a.lese())  // 2
	fmt.Println(a.plese()) // 2
}

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 keineKlasse int // Deklaration eines neuen Datentyps übernimmt die Aufgaben einer "Klasse", hier mit den Eigenschaften des Datentyps "int"

func (self keineKlasse) 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 keineKlasse) rechenMethodeMitArgument(argument int) int {
	return int(int(self) * argument)
}

func main() {
	instanz := keineKlasse(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 Datentyp Interface ist eine Schablone der Methoden, welche ein Datentyp besitzen muß![4]
  • Ein Interface wird benötigt um Polymorphie zu ermöglichen.[5]
  • Ein Interface wird implementiert, wenn alle geforderten Methoden implementiert werden.
Anmerkung: 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 Interfaces Reader und Writer des vorherigen Beispiels:
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. Go kennt jedoch (z.B. im Vergleich zu Java oder C++) keine "Generics" und es können keine überladenen Funktionen (Mehrfachdefinition mit unterschiedlichen Parameterlisten) definiert werden.
package main

import "fmt"
import "math"

//Schlechter Stil bei der Reihenfolge, dient hier der Anschaulichkeit (Besser: type interface - type struct - func)
type StructA struct {
	FieldA1, FieldA2 float64
}

func (thisA StructA) FMethod1() float64 {
	return thisA.FieldA1 * thisA.FieldA2
}
func (thisA StructA) FMethod2() float64 {
	return thisA.FieldA1 + thisA.FieldA2
}

type StructB struct {
	FieldB1 float64
}

func (thisB StructB) FMethod1() float64 {
	return math.Pow(thisB.FieldB1, 2)
}
func (thisB StructB) FMethod2() float64 {
	return math.Sqrt(thisB.FieldB1)
}

type IFace interface {
	FMethod1() float64
	FMethod2() float64
}

func FPolyfunc(i IFace) {
	fmt.Println(i, i.FMethod1(), i.FMethod2())
}

func FPolyfuncR(i IFace) IFace {
	return (i)
}

func main() {
	//1a. Implementierung der Methoden über die Struktur, …
	var a StructA
	var b StructB
	a = StructA{FieldA1: 10, FieldA2: 11} // a: = StructA{FieldA1: 10, FieldA2: 11}
	b = StructB{FieldB1: 16}              // b: = StructB{FieldB1: 16}

	//fmt.Println(a) //{10 11}
	//fmt.Println(b) //{12}

	//1b. … Implementierung des Interfaces im Funktionskopf
	FPolyfunc(a) //{10 11} 110 21
	FPolyfunc(b) //{16} 256 4

	//2. Implementierung der Methoden über das Interface (= identisches Resultat!)
	var c, d IFace = StructA{FieldA1: 10, FieldA2: 11}, StructB{FieldB1: 16}

	FPolyfunc(c) //{10 11} 110 21
	FPolyfunc(d) //{16} 256 4

	//3. Neuere Implementierungen überschreiben vorherige
	var e IFace

	e = StructA{FieldA1: 10, FieldA2: 11}
	fmt.Println(e, e.FMethod1(), e.FMethod2()) //{10 11} 110 21

	e = StructB{FieldB1: 16}
	fmt.Println(FPolyfuncR(e), FPolyfuncR(e).FMethod1(), FPolyfuncR(e).FMethod2()) // {16} 256 4
}

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 ("Auto-Boxing").
package main

import (
	"fmt"
)

/*
type universalTyper interface{}
var universalVar universalTyper
//oder direkt:
*/
var universalVar interface{}

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

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
}

Interface Type als veränderlicher Parameter

package main

import (
	"fmt"
)

type universaltype interface{}

func main() {
	fmt.Println(adder(1, 2, 3))       // 6
	fmt.Println(adder("A", "B", "C")) // "ABC"
}

func adder(args ...universaltype) universaltype {
	defer func() {
		if err := recover(); err != nil {

			fmt.Printf("Error: %v", err)
		}
	}()

	var (
		i_total int
		s_total string
		total   universaltype
	)

	for _, v := range args { // Iteration durch sämtliche Argumente
		switch t := v.(type) {
		default:
			panic(fmt.Sprintf("unexpected type %T\n", t))
		case int:
			i_total += v.(int)
			total = i_total
		case string:
			s_total += v.(string)
			total = s_total
		}
	}
	return total
}

Error-Handling

Datentyp error

  • Go ist beim Werfen von Fehlern nicht auf try-catch-finally Blöcke und Exceptions angewiesen, sondern nutzt hierzu den Datentyp error.
  • Nachfolgend zwei Beispiele für das Werfen von Fehlern, die die Funktion Error() des errors-Interfaces (type error interface { Error() string }) implementieren:
package main

import (
	"fmt"
	"time"
)

type ErrorMessage struct {
	Zeitpunkt time.Time
	Meldung   string
}

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

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

func main() {
	if err := reportError(); err != nil {
		fmt.Println(reportError()) // Um 2016-12-31 23:59:59.999999999 +0100 CET, Ach manno!
	}
}
package main

import (
	"fmt"
)

type ErrorMessage string

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

func reportError() error {
	return ErrorMessage("Na sowas!")
}

func main() {
	if err := reportError(); err != nil {
		fmt.Println(err) // Na sowas!
	}
}
  • Anstelle einer eigenen Funktion zur Implementierung des errors-Interfaces kann alternativ auch die Build-In-Funktion errors.New() zum Werfen eines Fehlers genutzt werden:
package main

import (
	"errors"
	"fmt"
)

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

func main() {
	if err := reportError(); err != nil {
		fmt.Println(err) // Grrr!
	}
}

Abfangen von Fehlern

defer func()
Das defer-Statement weist eine Funktion an, nach der return-Anweisung (also zum Zeitpunkt der Beendigung der umgebenden Funktion) ausgeführt zu werden.
panic()
Eine Build-In-Funktion zur sofortigen Unterbrechung der Funktionsauführung mit Fehlermeldung.
recover()
Fängt den panic-State einer Funktion ab zur Wiedererlangung der Kontrolle. Nur innerhalb von mit defer ausgelösten Funktionen sinnvoll.
package main

import "fmt"

func fehlerhafteFunktion() {
	defer func() { // 4.
		fmt.Printf("Es gab einen Fehler: \"%v\" Der Fehler wurde abgefangen!\n", recover()) // 5.
	}()
	fmt.Println("Bevor der Fehler ausgelöst wird …") // 2.
	panic("Jetzt schlägts dreizehn!")                // 3.
	fmt.Println("… und wenn kein Fehler ausgelöst worden wäre")
}

func main() {
	fehlerhafteFunktion()                      // 1.
	fmt.Println("Und hier gehts dann weiter!") // 6.
}

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

var chA chan int
chA = make(chan int)      // Der Channel wird in der Regel im Hauptprogramm definiert und wird wie eine Variable an die Unterprogramme übergeben.
chB := 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!
* Die Kapazität eines Channels ist auf maximal 100 Elemente beschränkt!

Channel I/O

ch <- v   // Sende v über Kanal ch.
v := <-ch // Empfange von ch und weise den empfangenen Wert v zu.


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

Channel-Puffer prüfen

select {
case ch <- true:
default:
	fmt.Println("Channel voll!")
}

oder:

if len(ch) == cap(ch) {
	fmt.Println("Channel voll!")
} else {
	fmt.Println("Alles gut!")
}

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/


GUIs

Das Qt – Applikation-Framework

IDE

  • Leicht aber erstaunlich mächtig und ausgereift: liteIDE

Links

Benchmark

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

Kritik

Anmerkungen

  1. Dieser und andere Aspekte werden sehr anschaulich erklärt von Peter Verhas: "Comparing Golang with Java", JavaDeep, April 27, 2016 und Christian Helmbold: "Pro und contra Go", 22. September 2013.
    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.
    Das derzeit möglicherweise stärkste deutschsprachige Plädoyer für Go findet sich auf entwickler.de. Dort stellt Sebastian Mancke die Frage: "Ist Golang das neue Java?" (29.5.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. Die Implementierung von Go-Interfaces im Vergleich zu Java-Interfaces wurde am 27. April 2016 von Peter Verhas sehr anschaulich beschrieben: "Interfaces are very simple in Go, and the same time very complex, or at least different from what they are in Java. Interfaces declare a bunch of functions that structs should implement if they want to be compliant with the interface. The inheritance is done the same way as in case of structs. The strange thing is that you need not specify in case of a struct that it implements an interface if it does. After all it is really not the struct that implements the interface, but rather the set of functions that use the struct or a pointer to the struct as receiver. If all the functions are implemented then the struct does implement the interface. If some of them are missing then the implementation is not complete.
    Why do we need the 'implements' keyword in Java and not in Go? Go does not need it because it is fully compiled and there is nothing like a classloader that loads separately compiled code during run-time. If a struct is supposed to implement an interface but it does not then this will be discovered during compile time without explicitly classifying that the struct does implement the interface. You can overcome this and cause a run-time error if you use reflection (that Go has) but the 'implements' declaration would not help that anyway."
    (Siehe https://javax0.wordpress.com/2016/04/27/comparing-golang-with-java/)
  5. Siehe auch Abschnitt Composite Types mittels Interface und Struct
  6. Auf Basis von BenchmarkPzf.go, BenchmarkPzf.py und BenchmarkPzf.java