Der Golang-Spicker

Aus archium.org
Wechseln zu: Navigation, Suche



😈 Die Programmiersprache Go ("Golang") 😈

Warum Go? Achtung - subjektiv!

Go ist eine von Grund auf klug durchdachte und in jeder Hinsicht ästhetische Programmiersprache.

Das offizielle Maskottchen – der Go-Gopher von Renee French

Bei Betrachtung drei populärer Rankings für Programmiersprachen (PyPl-Index, Tiobe-Index, und Languish) fällt trotz der deutlichen Abweichungen bei Ermittlung der Rankings auf, daß langjährige Platzhirsche wie C/C++ und Java an Beliebtheit einbüßen. Eindeutiger Liebling der Entwickler und die neue Nummer 1 ist 2024 die Sprache Python. Das ist kein Wunder, denn das Programming mit dieser schönen Programmiersprache macht Spaß und geht schnell von der Hand. Aber Python ist eine Skriptsprache und hinsichtlich Performance begrenzt. So ist auch eine ganze Anzahl junger Compilersprachen nachgerückt, die nun wieder mehr auf Typsicherheit und Performanceoptimierung Wert legen, als Entwickler 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[1].

Interessant ist aber auch, daß es zwar die großen IT-Riesen sind, die sich die Entwicklung eigener Programmiersprachen leisten, doch glücklicherweise können sie es sich trotzdem nicht erlauben, diese Entwicklung nicht unter eine Open Source 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 Programmiersprache 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ß Entwickler gewohnte Konstrukte wie die Klassen-Definition, aber auch try-catch-finally-Blöcke zum Abfangen von Exceptions nicht mehr benötigen.[2] 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 Programmiersprache wird die Nebenläufigkeit und die Kommunikation zwischen parallel laufenden Routinen so trivial wie bei Go.[3]

Aber da gerade kritisches Infragestellen oft die Vorstufe glühendster Begeisterung ist, sollen hier auch die "5 Dinge, die man an Go hassen kann (aber nicht muss)" unbedingt zur Lektüre empfohlen werden, die Christoph Engelbert am 10. April 2018 auf jaxenter.de[4] zur Programmierung mit Go zusammengetragen hat. So kann sich jeder einen eigenen Eindruck erarbeiten, ob er die Besonderheiten der Programmiersprache nun als Vorteil oder als Nachteil, als Fortschritt oder als Rückschritt betrachten möchte.

Go ist einfach anders!

Inhaltsverzeichnis

Ein Go-Spickzettel ("Cheat Sheet")

Compiler

Gc

Gc ist der Go-Compiler. Seit Version 1.5. wird 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 github.com/therecipe/qt
$ go get -u github.com/golang/lint/golint # -u = Lade update für bereits bestehende Packages

Einblick in den Assembler-Code

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

$ go build -gcflags -S byebyeJava.go

Compilieren mit Makefile

# Basic Go makefile

GOCMD=/usr/local/bin/go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get


all: build test clean

build: 
        $(GOBUILD) -v
dbg-build:
        $(GOBUILD) -v -gcflags=all="-N -l" -tags debug
test: 
        $(GOTEST) -v ./...
clean: 
        $(GOCLEAN)

Auszuführen beispielsweise mit:

$ make
$ make all
$ make build

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[5] Der Gccgo ist zwar langsamer, erzeugt aber mit der Programmierung effizienteren Maschinencode und dynamisch gelinkte Binaries!

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

Cross-Compiling

(Siehe https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 für eine umfassende Liste der GOOS- und GOARCH-Umgebungsvariablen!)

In Linux eine 32bit-.exe-Datei für Windows erzeugen

$ GOOS=windows GOARCH=386 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 das Raspberry PI2 (ARMv6) erzeugen

$ GOOS=linux GOARCH=arm GOARM=6 go build byebyeJava.go

Eine ausführbare Datei für das Raspberry PI3 (ARMv7) erzeugen

$ GOOS=linux GOARCH=arm GOARM=7 go build byebyeJava.go

Eine ausführbare Datei für 64Bit-ARM-Prozessoren (ARMv8, z.B. Rock64 und Ubuntu-Touch auf Volla Phone) erzeugen

$ GOOS=linux GOARCH=arm64 go build byebyeJava.go # GOARM=8 wäre unzulässig!

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

Statische Verlinkung der glibc-Libraries (Linux)

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-w -extldflags "-static"' -o toolbox *.go

(Bitte GOARCH berücksichtigen, das Beispiel erzeugt ein Binary für 64Bit-ARM-Prozessoren.)

Zu den ldflags siehe "My favorite build options for Go" (https://opensource.com/article/22/4/go-build-options) und die Manual Page (man ld)

Cross-Compiling unter Linux für Windows mit MXE

MXE (M cross environment) erleichtert die Codegenerierung für sehr viele (freie) Windows-Libraries (z.B. SDL, Qt, GTK, ImageMagick, etc.) unter Linux ganz enorm, indem es die benötigte Infrastruktur aus Compiler und Libraries einfach über ein Makefile erzeugt:

  • Installation von MXE
$ cd /mein/wunsch/pfad/zu/
$ git clone https://github.com/mxe/mxe.git
$ cd mxe
  • Erzeuge z.B. alle Packages für alle Installationsziele
$ make MXE_TARGETS='i686-w64-mingw32.static i686-w64-mingw32.shared x86_64-w64-mingw32.static x86_64-w64-mingw32.shared'
  • Aber es genügt zunächst die Erzeugung des GCC-Kompilers nur für das benötigte Installationsziel
$ make MXE_TARGETS='x86_64-w64-mingw32.static' gcc
Anmerkung
Die Umgebungsvariable MXE_TARGETS kann auch in Datei settings.mk definiert werden und muß dann nicht angegeben werden
  • Weitere Pakete können einfach hinzuinstalliert werden. Z.B. qt5. (Auch Mehrere Jobs und Compiler-Prozesse sind möglich!)
$ make qt5 --jobs=4 JOBS=2
Anmerkung
Details über die Installationsziele und auswählbaren Packages: https://mxe.cc/#usage und https://mxe.cc/#packages
  • Kompiliere go unter Linux für 32bit-Windows
$ cd /mein/projekt/pfad
$ CGO_ENABLED=1 CC=/mein/wunsch/pfad/zu/mxe/usr/bin/i686-w64-mingw32.static-gcc CXX=/mein/wunsch/pfad/zu/mxe/usr/bin/i686-w64-mingw32.static-g++ GOOS=windows GOARCH=386 go build -o meineApp.exe .
Konkret
CGO_ENABLED=1 CC=/usr/local/src/mxe/usr/bin/i686-w64-mingw32.shared-gcc CXX=/usr/local/src/mxe/usr/bin/i686-w64-mingw32.shared-g++ GOOS=windows GOARCH=386 go build -o meineApp.exe

Packages

Verzeichnisstruktur

Wichtige Umgebungsvariablen

Installation der Go-Systemdateien
$ export GOROOT='/usr/local/go'
Workspace für den eigenen Code
$ export GOPATH='/home/me/workspace/'
Eigene Git-Repositories für go modules[6]
$ export GOPRIVATE='*.schnulli.com,git.home.at/myZeugs'

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 importieren

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"
*/

/*
//Lokale Pfade werden als relative Pfade mit ./ begonnen
import ./meinPackage
*/

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 erzeugen

Benennung

package myPackageName // Die Deklaration des Packages erfolgt immer in der Kopfzeile eines 
                      // Source-Dokuments (Leerzeichen und Kommentare davor sind erlaubt)
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 während der Programmierung z.B. Funktionen sehr einfach in getrennte Dateien auslagern.

go modules

  • Seit go1.11 ist es möglich, und seit go1.13 ist es Standard, Projekte auch außerhalb des $GOPATH anzulegen. Die Pfadinformation steht dann in der Datei go.mod im Projektordner. Erzeugt wird die Datei go.mod durch:
$ go mod init mein/projekt/pfad

bzw.

$ go mod init

wenn ein import comment hinter der package-Anweisung im Code auf diesen Pfad hinweist:

// goModTest project main.go
package main // import "mein/projekt/pfad"
Example
  • Erzeuge den Go-Code ...
// goModTest project main.go
package main // import "goModTest"

import (
	"fmt"
	"os"

	"github.com/hackebrot/turtle"
)

func main() {
	emotion := "angry"
	emoji, ok := turtle.Emojis[emotion]

	if !ok {
		fmt.Fprintf(os.Stderr, "Das Zeichen \"%v\" konnte nicht gefunden werden\n", emotion)
		os.Exit(1)
	}

	fmt.Println(emoji.Char) //😠
}
  • ... und generiere die Datei go.mod ...
$ go mod init
  • Lade die Dependencies
$ go mod tidy
  • Es entsteht folgende Datei go.mod:
module goModTest

require (
        github.com/google/go-cmp v0.2.0 // indirect
        github.com/hackebrot/go-repr v0.0.0-20170331152400-586d894a5734 // indirect
        github.com/hackebrot/turtle v0.1.0
)
  • Der Code ist dann auch außerhalb des $GOPATH kompilierbar.
  • Eigene Git-Repositories sind mit $GOPRIVATE zu öffnen.

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.

Es können auch mehrere init()-Funktionen hintereinander aufgerufen und es können globalen Variablen Werte zugewiesen werden. Zum defer-Statement siehe den Abschnitt Error Handling:

package main

import "fmt"

var zähler int

func init() {
	fmt.Println("Die erste init-Funktion wurde aufgerufen") //1
	zähler++
}

func init() {
	defer fmt.Println("wurde aufgerufen")  //3
	fmt.Print("Die zweite init-Funktion ") //2
	zähler++
}

func main() {
	defer fmt.Println("(Und dann kann mit defer sogar *nach* der main-Funktion eine Funktion ausgeführt werden!)") //6
	fmt.Println("Die main-Funktion wurde aufgerufen")                                                              //4
	fmt.Printf("Die init-Funktion wurde zuvor %dmal aufgerufen\n", zähler)                                         //5
}

/*
Die erste init-Funktion wurde aufgerufen
Die zweite init-Funktion wurde aufgerufen
Die main-Funktion wurde aufgerufen
Die init-Funktion wurde zuvor 2mal aufgerufen
(Und dann kann mit defer sogar *nach* der main-Funktion eine Funktion ausgeführt werden!)
*/

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 und Rückgabeparemater werden im Funktionskopf deklariert:

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 letzten 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.
  • Es können auch KEINE Parameter übergeben werden.
func main() {
	fmt.Println(adder(1, 2, 3)) // 6
	fmt.Println(adder(9, 9))    // 18
	fmt.Println(adder())        // 0

	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() {
	// Bedenke, auch Funktionen sind auch ein Datentyp! 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
}

  • Anonyme Funktionen sind der einzige Weg innerhalb einer anderen Funktion eine Funktion zu deklarieren!

Der Datentyp einer Funktion

  • In Go hat eine Funktion auch einen für sie charakterischen Datentyp
package main

import "fmt"

func main() {
	var p func(...interface{}) (int, error)
	p = fmt.Print //Funktion OHNE Parameterübergabe = Datentyp der Funktion
	p("Hallo")

	pl := fmt.Println //In der Praxis geht das natürlich etwas kompakter
	pl(" Welt")
} //Hallo Welt

Funktionen mit Gedächtnis: Closures (wenn Funktionen Funktionen zurückgeben)

  • Closure-Funktionen machen es sich zunutze, auf Variablen des Eröffnungskontextes lesend und auf die eigenen Kopien dieser Variablen schreibend zugreifen zu können! Die Funktionsweise ist erst auf den zweiten Blick logisch: Eine eingeschlossene Funktion wird bei ihrem ersten Aufruf die Variable aus dem Eröffnungskontext lesen, eine Kopie anfertigen und bei allen weiteren Aufrufen mit ihrer eigenen Kopie arbeiten. Den Status ihrer eigenen Kopie wird sie sich merken!
Der "Eröffnungskontext"
ist der Teil der äußeren Funktion, der vor der zurückgegebenen Funktion ausgeführt wird. er wird nur ein einziges Mal, nämlich bei der Variablenzuweiseung ausgeführt.
package main

import (
	"fmt"
)

func main() {
	var cf func() int
	cf = meineFunktion()
	// gleichbedeutend mit:
	// cf := meineFunktion()

	fmt.Println(cf()) //1
	fmt.Println(cf()) //2
	fmt.Println(cf()) //3
	/*
		Äußere Funktion
		Innere Funktion
		1
		Innere Funktion
		2
		Innere Funktion
		3
	*/

	fmt.Println(meineFunktion()()) //1
	fmt.Println(meineFunktion()()) //1
	fmt.Println(meineFunktion()()) //1
	/*
		Äußere Funktion
		Innere Funktion
		1
		Äußere Funktion
		Innere Funktion
		1
		Äußere Funktion
		Innere Funktion
		1
	*/
}

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){}"
	fmt.Println("Äußere Funktion")

	var outerVar = 0 // outerVar := 0
	meinErgebnis := func() int {
		fmt.Println("Innere Funktion")
		outerVar++
		return outerVar
	}
	return meinErgebnis
}

  • Natürlich sind auch Übergabeparameter zulässig:
package main

import (
	"fmt"
	"strconv"
)

func main() {
	cf := meineFunktion()
	fmt.Println(cf("a")) //a1
	fmt.Println(cf("b")) //b2
	fmt.Println(cf("c")) //c3
}

func meineFunktion() func(string) string {
	outerVar := 0
	meinErgebnis := func(eingabestring string) string {
		outerVar++
		return eingabestring + strconv.Itoa(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
	}
}

Rekursiver Funktionsaufruf

... ist möglich:

package main

import "fmt"

func SeventhSonofaSeventhSon(age int8, probability float32) float32 {
	age++
	probability = probability * 1 / 7
	fmt.Printf("Generation: %d; Probability: %.5f%%\n", age, probability)
	if age < 7 {
		SeventhSonofaSeventhSon(age, probability)
	}
	if age != 1 {
		return probability
	} else {
		return 100
	}

}

func main() {
	fmt.Printf("%.0f%% Iron Maiden 👍😁\n", SeventhSonofaSeventhSon(0, 100))
}

Speicherverwaltung

Anmerkung
new(Typ) ist eine Build-in-Funktion zum Zuweisen (bzw. Reservieren) von Speicher. new(Typ) gibt einen Zeiger *Typ auf die Speicheradresse zurück.
a := new(int)
*a = 23
fmt.Println(a,*a) //0xc42009c010 23
make(Typ, Länge, Kapazität) ist eine Build-in-Funktion zum Zuweisen (bzw. Reservieren) 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. Wird für die Zeigerarithmetik benötigt.

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
arrays
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`)
Anmerkung zur Mehrzeiligkeit von Strings
Raw-Strings können mehrzeilig sein! Einfache Strings können nur einzeilig sein, einzelne Zeilen müssen dann mit + aneinander gereiht werden.
Vergleiche 
`Zeile 1
Zeile 2
Zeile 3`
 mit 
"Zeile 1" +
"Zeile 2" +
"Zeile 3"

Einzelne Zeichen (rune literals)

fmt.Println(string('H'), 'a', string('l'), 'l', 'o') //H 97 l 108 111
fmt.Println([]rune{'A', 'B', 'C'})                   //[65 66 67]
fmt.Println([]byte{68, 69, 70})                      //[68 69 70]
fmt.Println(string([]rune{'A', 'B', 'C'}))           //ABC
fmt.Println(string([]byte{68, 69, 70}))              //DEF
fmt.Println(rune('ŋ'))                               // 331
fmt.Println(string(rune('ŋ')))                       // ŋ
fmt.Println(([]rune(string("ŋ")))[0])                // 331 // Wandele String in rune um

Ganzzahlen (integer literals)

fmt.Println(255, 0xff, 0Xff, 0o377, 0O377, 0377, 0b11111111, 0B11111111)
/* Jeweils die Zahl 255 in Dezimal-, Hexadezimal-, Oktal- und Binär-Schreibweise.
Beachte, daß bis go1.13 eine Oktalzahl ohne o/O geschrieben wurde; diese Schreibweise bleibt weiter gültig!*/

Fließkommazahlen (floating point literals)

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

//Auch Fließkommazahlen können seit go1.13 hexadezimal geschrieben werden. Hinter der Mantisse muß ein Exponent in Dezimalschreibweise stehen, getrennt durch p/P. Der Exponent potenziert die Basis 2 um den Wert des Exponenten:
fmt.Println(0x8.ap3, 0xf.7p-2) //69 (= 0x8,a * 2³ ) 3.859375 (= 0xf,7 * 2⁻²)
//Alternative Schreibweise:
fmt.Println((0x8.ap0)*math.Exp2(3), (0xf.7p0)*math.Exp2(-2)) //69 3.859375

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)
fmt.Println(0xai, 0b101i, 0o7i)                                          //(0+10i) (0+5i) (0+7i); Auch imaginäre Zahlen können (seit go1.13) hexadezimal, oktal oder binär geschrieben werden.

Trennzeichen in Zahlen (digits separators) – Ohne weitere Funktion, nur Code-Kosmetik!

fmt.Println(1_234.56_78_90) //1234.56789

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 MaxUint = ^uint(0) // Auch die Ergebnisse von Operationen können zugewiesen werden
var MinUint = 0
var MaxInt = int(MaxUint >> 1)
var MinInt = -MaxInt - 1
var c = 42      // Deklaration und Initialisierung durch Typinferenz
c := 42         // dasselbe, alternative Schreibweise
var c = int(42) // dasselbe mit Typumwandlung zur Typbestimmung

Konstanten

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

const (
	f = 1 << iota // 1
	g = 1 << iota // 2
	h = 1 << iota // 4
	i = 1 << iota // 8
	j = 1 << iota // 16
)


func main() {
	println(a, b, c, d, e) //0 1 2 3 0
	println(f, g, h, i, j) //1 2 4 8 16
}

Anmerkung
Automatische Aufzählungen (Enumerationen) lassen sich über die Zuweisung des Schlüsselwortes iota an die Konstante umsetzen.

Type-Declaration

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

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

Type-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
package main

import "fmt"

func main() {
	// Pointer auf einen Datentyp
	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

	// Pointer auf ein Array eines Datentyps
	var e [4]byte
	var f *[4]byte
	var g *byte
	e = [4]byte{22, 33, 44, 55}
	f = &e                     //Bedenke: Wenn die Startadresse eines Arrays benötigt wird …
	g = &e[0]                  // … dann muß auf das erste Element des Arrays gezeigt werden! (z.B. bei CGO sehr wichtig!)
	fmt.Println(&e, e, &e[0])  //&[22 33 44 55] [22 33 44 55] 0xc0000141e0
	fmt.Println(f, &(f[0]), g) //&[22 33 44 55] 0xc0000141e0 0xc0000141e0
}

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)
Wertzuweisung an Pointer kann alternativ auch indirekt erfolgen
// z.B. über anonyme Funktion …
var c *float64
c = func(f float64)*float64 {return &f}(123.456)
// …oder Variable
var c *float64
f := 123.456
c = &f

Zeigerarithmetik (wie in C) gibt es in Go eigentlich nicht …

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

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 (typisierten) 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 (typisierten) Pointer um
	fmt.Println(pi2, *pi2) //0xc42000e2f2 123

	// Mit dem Datentyp "uintptr" kann man dann auch rechnen:
	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 (typisierten) Pointer

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

	println(unsafe.Sizeof(i))  //6 (Ermittle die Größe der gesamten Variable in Bytes)
	println(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(unsafe.Offsetof(j.a)) //0 (Nur bei Strukturen: Offset eines Elements in Relation zur Basis
	println(unsafe.Offsetof(j.b)) //8
	println(unsafe.Alignof(j.a))  //1 (1 Byte bei int8)
	println(unsafe.Alignof(j.b))  //8
	println(unsafe.Alignof(j))    //8
	// Beachte, daß der tatsächliche Platzbedarf von der Speicherausrichtung abweichen kann!
	println(unsafe.Sizeof(j.a)) //1
	println(unsafe.Sizeof(j.b)) //16
	println(unsafe.Sizeof(j))   //24
}

Typumwandlung (type casting)

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{{"α", "β"}, {"γ", "δ"}}
Pointer auf Arrays
var a *[2]int // Pointer auf das Array aus int
var b [2]*int // Array eines Pointers auf int

a = new([2]int) // Notwendig zur Speicherallozierung

a[0] = 11
c := 12
b[0] = &c
fmt.Println(a, *a, (*a)[0]) // &[11 0] [11 0] 11
fmt.Println(b, b[0], *b[0]) // [0xc420012168 <nil>] 0xc420012168 12

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()
package main

import "fmt"

func main() {
	var a []int
	// 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
	fmt.Println(a, cap(a), len(a)) // [0 0 0 0 0] 10 5
	a = make([]int, 5)             // Kapazität ist optional
	fmt.Println(a, cap(a), len(a)) // [0 0 0 0 0] 5 5

	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
	m[0] = make([]string, 2)
	m[0] = []string{"123", "abc"}
	fmt.Println(m) // [[123 abc] [] [] [] [] [] [] [] [] []]

	// Erzeugung eines Pointers auf ein Slice mit new
	var p *[]int = new([]int) // Alloziiert einen Slice mit nil als Inhalt und gibt einen Pointer zurück (*p == nil)
	fmt.Println(*p)           // []
	*p = make([]int, 2, 2)    // make wird weiterhin benötigt, um den Speicher zu alloziieren
	(*p)[0] = 10
	(*p)[1] = 20
	fmt.Println(*p, cap(*p), len(*p)) // [10 20] 2 2
}

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]
Kopiere einen Teil des Slice in einen Teil eines anderen Slices
package main

import "fmt"

var a = []string{"J", "a", "v", "a"}
var b = []string{"G", "o", "p", "h", "e", "r"}

func main() {
	fmt.Println(a) //[J a v a]
	fmt.Println(b) //[G o p h e r]

	copy(a[2:4], b[0:2])
	fmt.Println(a) //[J a G o]
}

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 aller Elemente
fmt.Println(slice) //[a b c d e f g]
Reset eines Slices mit clear()
var a = []int {1, 2, 3, 4}
var b = []string {"1", "2", "3", "4"}
fmt.Println(a) //[1 2 3 4]
fmt.Println(b) //[1 2 3 4]
clear(a) //Setzt alle Werte eines Slices auf den default-Wert
clear(b)
fmt.Println(a) //[0 0 0 0]
fmt.Println(b) //[   ]
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 – auch verschachtelt ...
package main

import "fmt"

func main() {
	var m map[string]int
	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)

	clear(m) //Löscht alle Elemente einer Map
	fmt.Println(m)

	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"])

	mdm := map[string]map[string]map[int]int{ // Multidimensionale Maps sind möglich, wenn auch etwas sperriger, als Dictionaries in Python oder Arrays in PHP
		"color": {"green": {1: 0x00ff00, 2: 0x00ee00}}}
	fmt.Println(mdm)

	//var mdm2 map[string]map[string]map[int]int
	//mdm2 = make(map[string]map[string]map[int]int)
	// oder
	mdm2 := make(map[string]map[string]map[int]int)
	mdm2["color"] = make(map[string]map[int]int)
	mdm2["color"]["red"] = make(map[int]int)
	mdm2["color"]["red"][1] = 0xff0000
	fmt.Println(mdm2["color"]["red"][1])
}

  • … 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}

for i, v := range pow {
	fmt.Printf("2**%d = %d\n", i, v)
}

for j := range pow { // Identical result, may omit value
	fmt.Printf("2**%d = %d\n", j, pow[j])
}

"Generics" - Kombinierte Datentypen

Ab go1.18 ist es möglich, Funktionen generische Datentypen zu übergeben, womit ein lange beklagter Nachteil von Go gegenüber anderen Sprachen wie Java oder C++ behoben ist.

package main

import "fmt"

//Die Definition der Type-Argumente erfolgt in eckigen Klammern vor der Variablendeklaration
func dividiereAlleZahlen[num int8 | int16 | int32 | int64 | int | float32 | float64](a,b num) (c num){
	c = a/b
	return c
}

// Vereinfachung komplexer Datentype-Kombinationen erfolgt durch Type-Constraints.
// Type-Constraints sind eigentlich nur Platzhalter für die Regeln, die eine
// Kombination anderer Daten beschreiben.
// Das Kombinieren verschiedner Datentypen geschieht mit dem |-Operator
type NumberConstraint interface { // Erzeugung eines Type-Constraints
   int8 | int16 | int32 | int64 | int | float32 | float64
}
// Beachte! Type-Constraints sind jedoch KEINE eigenständigen Datentypen
// "var Zahl NumberConstraint" wäre unzulässig!


//TypArgumente werden in der Reihenfolge der Variablen-Deklartion in den Funktionsparametern angegeben.
// Zur Vereinfachung können Datentyp ketten durch Type-Constraints umschrieben werden
func eineFunktionMitMehrenTypArgumenten[idx string|[]byte, str string, num NumberConstraint](i idx, m map[str]num) (z num) {
	z = m[str(i)] //Typumwandlung in den generischen Typ beachten!
    return z
}

func main() {

	d := dividiereAlleZahlen(8.0,2.0) // Wenn möglich, werden Datentypen per Typinferenz übergeben.
	fmt.Printf("%v, %T\n",d,d) //4, float64

	//e := dividiereAlleZahlen(8,2.0) // Wenn nicht möglich, wird ein Fehler geworfen:
	//fmt.Printf("%v, %T\n",e,e) //default type float64 of 2.0 does not match inferred type int for num

	f := dividiereAlleZahlen(float32(8),2.0) // Entscheidend ist, daß die Datentypen im Sinne der Funktionsdefinition miteinander operieren können.111111111
	fmt.Printf("%v, %T\n",f,f) //4, float32

	g := dividiereAlleZahlen[int8](8,2.0) // Ist Typinferenz nicht möglich, müssen zusätzlich Type-Argumente (in eckigen Klammern) übergeben werden!
	fmt.Printf("%v, %T\n",g,g) //4, int8

	h := eineFunktionMitMehrenTypArgumenten("indexI", map[string]float64{"indexI":12.345})
	fmt.Printf("%v, %T\n",h,h) //12.345, float64

	i := eineFunktionMitMehrenTypArgumenten[[]byte,string,int8]([]byte("indexII"), map[string]int8{"indexII":127})//Die Type-Argumente dienen nur der Veranschaulichung;  sie wären hier nicht notwendig, da Typinferenz aufgrund der eindeutigen Datentypen möglich wäre. Die Reihenfolge der Type-Argumente orientiert sich an der Reihenfolge der Variablendeklaration in den Funktionsparametern.
	fmt.Printf("%v, %T\n",i,i) //127, int8
}

Vordefinierte Type-Constraints

comparable Alle Datentypen, die mit == und != verglichen werden können.
any Alle Datentypen.[7] (any ist ein Type-Alias zu interface{})

Strukturen

Definition und Zugriff

package main

import "fmt"

type programmingLanguages struct {
	name    string
	rating  int
	future  bool
	float64 //Anonymes Feld
	string  //Anonymes Feld
}

func main() {

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

	// 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
	var a programmingLanguages
	a = programmingLanguages{"Go", 1, true, 1.23, "compiler"}
	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)      //{php 4 true 0.815 interpreter} (Zeiger auf b!)

	// Leere Strukturen prüfen
	if a != (programmingLanguages{}) { // Beachte die zusätzlichen Klammern ()
		fmt.Println("Die Struktur a ist nicht leer")
	}
}

Der Funktionsdatentyp in Strukturen

package main

import "fmt"

type dataType struct {
	s string
	i int
	b bool
	f func(string) string
}

func main() {
	var data dataType
	data = dataType{s: "Hallo", i: 12345, b: true, f: func(a string) string { return data.s + " " + a }}
	fmt.Println(data.f("Welt"), data.i, data.b) //Hallo Welt 12345 true
}

Verschachtelte (nested) Strukturen

package main

import (
	"fmt"
)

func main() {
	// Verschachtelte Strukturen können entweder einzeln definiert werden ...
	type c struct {
		ca string
		cb bool
	}

	type nested struct {
		a int
		b float64
		c
	}

	var sn nested
	sn = nested{a: 1, b: 1.23, c: c{ca: "xyz", cb: true}}
	fmt.Println(sn) //{1 1.23 {xyz true}}

	// ... oder "anonym" gemeinsam mit der obergeordneten Struktur definiert werden
	type nestedanonymous struct {
		a int
		b float64
		d struct {
			da string
			db bool
		}
		e []struct {
			ea string
			eb bool
		}
	}

	var sa, sb nestedanonymous
	sa.a = 2
	sa.b = 3.45
	sa.d.da = "abc"
	sa.d.db = true
	sa.e = make([]struct {
		ea string
		eb bool
	}, 2)
	sa.e[0].ea = "def"
	sa.e[0].eb = true
	sa.e[1].ea = "ghi"
	sa.e[1].eb = false
	fmt.Println(sa) //{2 3.45 {abc true} [{def true} {ghi false}]}

	sb = nestedanonymous{a: 3, b: 4.56, d: struct {
		da string
		db bool
	}{da: "αβγ", db: true}, e: []struct {
		ea string
		eb bool
	}{{ea: "δεζ", eb: true}, {ea: "ηθι", eb: false}}}
	fmt.Println(sb) //{3 4.56 {αβγ true} [{δεζ true} {ηθι false}]}
}

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 AND NOT 00000011 = 000000100)
^() bitweise NEGATION (^byte(1) = 254, da 00000001 zu 11111110 wird)
<< 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)
Anmerkung
Arithmetische Operatoren können mit dem Zuweisungsoperator kombiniert werden
x += y entspricht x = x + y
x |= y entspricht x = x | y

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

Klammern

() begrenzen den Gültigkeitsbereich eines Ausdrucks. Z.B. entscheidet
   bei Strukturen die Klammer über die Adresszuordnung eines Pointers.
   (Ohne Klammern zeigt der Pointer immer auf das letzte Element!)
   Vergleiche (*struktur).element oder *struktur.element

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 (Sprungmarken) 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) und führe diese aus. Es findet keine erneute Prüfung statt!

Bedingungen

func main() {
	if x > 0 {
		return x
	} else {
		return -x
	}
	// Vor die Bedingung kann ein Deklarationsstatement gesetzt werden. Im Example 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 mit Überprüfung einer Expression

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

... oder auch mit Rückgabewert einer Funktion als Expression

switch liefereM() {
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 Expression (bzw. Standardannahme "true"), stattdessen erhält die jeweilige case-Bedingung eine Expression

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("👻")
	}

Auch einem Switch ohne Expression können Variablendeklarationen vorneweg gestellt werden (entscheidend ist das Semikolon ;)

switch os := getMyOS(); {
	case os == "windows": fmt.Println("😱")
	case os == "mac": fmt.Println("😚")
	case os == "linux": fmt.Println("😊")
	case os == "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 die Objekteigenschaften über drei Basis-Komponenten darstellen:
    1. Datentypen: In Go kann jeder selbstdefinierte Datentyp zum Objekt werden, also die Eigenschaften übernehmen, die in anderen OOP-Sprachen in der Regel eine Klasse hat.
    2. Methoden: Anders als aus anderen OOP-Sprachen gewöhnt, sind Methoden in Go keine innerhalb einer Klasse definierten Funktionen. Vielmehr werden Funktionen in Go außerhalb eines Datentyps definiert und durch Angabe eines Empfänger-Datentyps (für die "Receiver-Variable") als Methode dieses Datentyps deklariert.
    3. Interfaces: Mehrere Methoden können in Go zu Interfaces zusammengebunden werden.
  • 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"

// Der Verbundtyp übernimmt die Klassendefinition
type noClass struct {
	a string
	b int
	c bool
}

//
func (self noClass) method() int {
	return self.b + self.b // "self" ist kein Schlüsselwort in go, die Empfänger-Variable wurde hier nur zur Veranschaulichung so genannt
}

// Pointer sind effizienter und sie gestatten Veränderungen der Variable durch Nennung des Speicherbereichs einer Variable anstelle sie zu kopieren
func (this *noClass) pointerMethod() int {
	return this.b * this.b // "this" ist kein Schlüsselwort in go, die Empfänger-Variable wurde hier nur zur Veranschaulichung so genannt
}

type noClass2 struct {
	noClass // Durch die Einbindung des Typs noClass, "erbt" noClass2 dessen Eigenschaften!
	d       float64
}

type noClass3 struct {
	noClass2 // Durch die Einbindung des Typs noClass2, "erbt" noClass3 dessen Eigenschaften – und natürlich auch die Eigenschaften von noClass!
	e        rune
}

func main() {
	// Das Setzen von Parametern ähnelt der Übergabe von Parametern an einen OOP-Konstruktor
	entity := noClass{a: "Blabla", c: true, b: 123}
	entity2 := noClass2{noClass: entity, d: 4.56789}
	entity3 := noClass3{noClass2: entity2, e: '🐧'}

	// Beachte die vielfältigen Möglichkeiten auf eigene und geerbte Elemente zuzugreifen:
	fmt.Println(entity.a, entity.b, entity.c)                                                                                                                                                                                                                                                                                                                                                                                          // Blabla 123 true
	fmt.Println(entity2.a, entity2.b, entity2.c, entity2.d, entity2.noClass.a, entity2.noClass.b, entity2.noClass.c, entity.a, entity.b, entity.c)                                                                                                                                                                                                                                                                                     // Blabla 123 true 4.56789 Blabla 123 true Blabla 123 true
	fmt.Println(entity3.a, entity3.b, entity3.c, entity3.d, entity3.e, entity3.noClass.a, entity3.noClass.b, entity3.noClass.c, entity3.noClass2.a, entity3.noClass2.b, entity3.noClass2.c, entity3.noClass2.d, entity3.noClass2.noClass.a, entity3.noClass2.noClass.b, entity3.noClass2.noClass.c, entity2.a, entity2.b, entity2.c, entity2.d, entity2.noClass.a, entity2.noClass.b, entity2.noClass.c, entity.a, entity.b, entity.c) // Blabla 123 true 4.56789 128039 Blabla 123 true Blabla 123 true 4.56789 Blabla 123 true Blabla 123 true 4.56789 Blabla 123 true Blabla 123 true

	// Es ist zu beachten, daß Go die Konversion zwischen Werten und Pointern bei Methodenaufrufen automatisch vollzieht und immer Werte zurückliefert!
	entityp := &entity
	entity2p := &entity2
	entity3p := &noClass3{noClass2: *entity2p, e: '🐧'}
	fmt.Println(entity.method(), "==", entityp.method())                                   //246 == 246
	fmt.Println(entity.method(), "==", entity2p.noClass.method())                          //246 == 246
	fmt.Println(entity3p.noClass2.noClass.pointerMethod(), "==", entity2p.pointerMethod()) //15129 == 15129

	// Das Überschreiben von Paramtern hat nur innerhalb der eigenen Instanz Auswirkungen, ...
	entity2.a = "Laber"
	entity2.b = 246
	fmt.Println(entity3.a, entity3.b, entity3.c, entity3.d, entity3.e, entity3.noClass.a, entity3.noClass.b, entity3.noClass.c, entity3.noClass2.a, entity3.noClass2.b, entity3.noClass2.c, entity3.noClass2.d, entity3.noClass2.noClass.a, entity3.noClass2.noClass.b, entity3.noClass2.noClass.c, entity2.a, entity2.b, entity2.c, entity2.d, entity2.noClass.a, entity2.noClass.b, entity2.noClass.c, entity.a, entity.b, entity.c) // Blabla 123 true 4.56789 128039 Blabla 123 true Blabla 123 true 4.56789 Blabla 123 true *Laber* *246* true 4.56789 *Laber* *246* true Blabla 123 true

	// ... auch bei Zeigern auf Instanzen
	entityp.a = "Sülz"
	entityp.b = 369
	fmt.Println(entity3p.a, entity3p.b, entity3p.c, entity3p.d, entity3p.e, entity3p.noClass.a, entity3p.noClass.b, entity3p.noClass.c, entity3p.noClass2.a, entity3p.noClass2.b, entity3p.noClass2.c, entity3p.noClass2.d, entity3p.noClass2.noClass.a, entity3p.noClass2.noClass.b, entity3p.noClass2.noClass.c, entity2p.a, entity2p.b, entity2p.c, entity2p.d, entity2p.noClass.a, entity2p.noClass.b, entity2p.noClass.c, entityp.a, entityp.b, entityp.c) //Blabla 123 true 4.56789 128039 Blabla 123 true Blabla 123 true 4.56789 Blabla 123 true Laber 246 true 4.56789 Laber 246 true *Sülz* *369* true
}

Wo Pointer-Receiver 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.[8]
package main

import "fmt"

type noClass struct {
	X int
}

func (this noClass) setter1() {
	this.X = 1
}
func (this *noClass) point_setter1() {
	this.X = 2
}
func (this noClass) getter1() int {
	return this.X
}
func (this *noClass) point_getter1() int {
	return this.X
}

func main() {
	var a noClass = noClass{}
	a.setter1()
	fmt.Println(a.getter1())       // 0 und nicht etwa 1
	fmt.Println(a.point_getter1()) // 0 und nicht etwa 1
	a.point_setter1()
	fmt.Println(a.getter1())       // 2
	fmt.Println(a.point_getter1()) // 2

	// или

	var b *noClass = &noClass{}
	b.setter1()
	fmt.Println(b.getter1())       // 0
	fmt.Println(b.point_getter1()) // 0
	b.point_setter1()
	fmt.Println(b.getter1())       // 2
	fmt.Println(b.point_getter1()) // 2
}

Statische Methoden bzw. statischer Methodenaufruf

package main

import "fmt"

type myStructType struct {
	X int
}

func (s myStructType) doSomething(v int) {
	fmt.Println("I got", v)
	s.X = v //X wird eigentlich nicht benötigt
}
func (s *myStructType) setValue(v int) {
	fmt.Println("I set", v)
	s.X = v
}
func (s *myStructType) getValue() int {
	return s.X
}

func main() {
	var a myStructType = myStructType{123}
	//Statischer Methodenaufruf, das erste zusätzliche Argument ist das Object!
	fmt.Println("I read", (*myStructType).getValue(&a)) // 123
	myStructType.doSomething(a, 1)                      // 1
	(*myStructType).setValue(&a, 2)                     // 2
	fmt.Println("I read", (*myStructType).getValue(&a)) // 2
}

Jeder selbstdefinierte 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() {
	var instanz keineKlasse
	instanz = 2
	//oder ebenfalls möglich:
	//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.
  • Ein Interface ist ein Datentyp mit methodischen Eigenschaften.
  • Ein Interface ist ein Datentyp, der zusammen mit allen seinen Methoden implementiert werden muß. [9]
  • Die Instanz eines Interfaces kennt nur Methoden, keine typabhängigen Felder![10]
  • Die Methoden eines Interfaces können unterschiedlichen Empfänger-Datentypen zugeordnet sein. Das hat zur Folge, daß ein Interface, das durch gar keine Methoden eingeschränkt wird, grundsätzlich die Werte aller Datentypen speichern kann!
  • Ein Interface wird benötigt, wenn nicht Werte als Parameter übergeben werden sollen, sondern Methoden, wie mit Werten umzugehen ist.
  • Ein Interface kann auch verwendet werden, um Polymorphie zu ermöglichen – gleichnamige Funktionen (bzw. Methoden) mit identischen Übergabe-Parametern können dann unterschiedliche Aufgaben übernehmen.[11]

Ein Interface hat keinen speziellen (also jeden) Datentyp

package main

import "fmt"

type datentyper interface {}

func main() {
	var a datentyper

	a = "abc"
	fmt.Println(a) // abc

	a = 123
	fmt.Println(a) // 123
}

Die Implementierung von Methoden ist nur für einen lokalen Datentyp möglich, auch beim Interface

package main

import (
	"fmt"
	"strconv"
	"time"
)

type datentyper interface {
	dosomething() string
}

type number int

func (receiver number) dosomething() string {
	return strconv.Itoa(int(receiver) + 456)
}

type text string

func (receiver text) dosomething() string {
	return string(receiver + "def")
}

/* Resultiert in Fehler:
./main.go:27:6: cannot define new methods on non-local type time.Time
func (receiver time.Time) dosomething() string {
	return string(receiver.String())
}

Deklariere stattdessen einen local type:
*/

type mytime time.Time

func (receiver mytime) dosomething() string {
	return string(time.Time(receiver).String())
}

func main() {
	var a datentyper

	a = text("abc")
	fmt.Println(a.dosomething()) //abcdef

	a = number(123)
	fmt.Println(a.dosomething()) // 579

	a = mytime(time.Now())
	fmt.Println(a.dosomething()) // 2020-04-14 16:17:52.159587904 +0200 CEST m=+0.000060994
}

Interface mit struct-Datentyp

package main

import (
	"fmt"
	"strconv"
)

type datentyper interface {
	dosomething() string
}

type datentyp struct {
	number int
	text   string
}

func (receiver datentyp) dosomething() string {
	return strconv.Itoa(int(receiver.number)) + string(receiver.text)
}

func main() {
	var a datentyper

	a = datentyp{number: 123, text: "abc"}
	fmt.Println(a.dosomething()) //123abc
}

Warum Interfaces mit struct-Datentyp dennoch keine Klassen sind

Anders als Klassen in Objektorientierten Programmiersprachen wie C++, Java oder Python, erlauben Go-Interfaces nur Zugriff auf ihre Methoden, nicht jedoch den Zugriff auf Ihre Attribute!

Folgendes Example entstammt der Phantasie und ist nicht lauffähig:

package main

import (
	"fmt"
	"strconv"
)

type datentyper interface {
	dosomething() string
	number        int    // UNGÜLTIG
	text          string // UNGÜLTIG
}

type datentyp struct {
	number int
	text   string
}

func (receiver datentyp) dosomething() string {
	return strconv.Itoa(int(receiver.number)) + string(receiver.text)
}

func main() {
	var a datentyper

	a = datentyp{number: 123, text: "abc"}
	fmt.Println(a.dosomething()) //123abc
	fmt.Println(a.number)        //123 ? UNGÜLTIG!
	fmt.Println(a.text)          //abc ? UNGÜLTIG!
}

Interface und Pointer-Receiver

  • Über Adresszuweisung, nicht Wertzuweisung (call by reference)[12]
package main

import "fmt"

type datentyper interface {
	set(int)
	pset(int)
	get() int
	pget() int
}

type datentyp struct {
	X int
}

func (receiver datentyp) set(i int) {
	receiver.X = i
}
func (receiver *datentyp) pset(i int) {
	receiver.X = i
}
func (receiver datentyp) get() int {
	return receiver.X
}
func (receiver *datentyp) pget() int {
	return receiver.X
}

func main() {
	var b datentyper
	//b = datentyp{} // falsch
	b = &datentyp{} // richtig
	b.set(1)
	fmt.Println(b.get())  // 0 und nicht etwa 1
	fmt.Println(b.pget()) // 0 und nicht etwa 1
	b.pset(2)
	fmt.Println(b.get())  // 2
	fmt.Println(b.pget()) // 2
}

Ein Interface kann auf unterschiedliche Weisen implementiert werden

Drei fast gleiche Beispiele:

Implementierung der geforderten Methoden über die Struktur; das "io.Writer"-Interface fordert einzig die Implementierung der Funktion "Write"

package main

import (
	"errors"
	"fmt"
)

type myStruct struct{}

func (ms myStruct) Write(p []byte) (int, error) {
	fmt.Println(p, string(p)) // [71 111 32 114 111 99 107 115] Go rocks
	return 1, errors.New("Irgendein Fehler")
}

func main() {
	var w myStruct
	fmt.Fprintf(w, "Go rocks")
}

Implementierung eines Interfaces mit selbst definierten Eigenschaften

package main

import (
	"errors"
	"fmt"
)

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

type myStruct struct{}

func (ms myStruct) Write(p []byte) (int, error) {
	fmt.Println(p, string(p)) // [71 111 32 114 111 99 107 115] Go rocks
	return 1, errors.New("Irgendein Fehler")
}

func main() {
	var w myInterface
	w = myStruct{}
	fmt.Fprintf(w, "Go rocks")
}

Implementierung eines Interfaces mit geerbten Eigenschaften

package main

import (
	"errors"
	"fmt"
	"io"
)

type myInterface interface {
	io.Writer
}

type myStruct struct{}

func (ms myStruct) Write(p []byte) (int, error) {
	fmt.Println(p, string(p)) // [71 111 32 114 111 99 107 115] Go rocks
	return 1, errors.New("Irgendein Fehler")
}

func main() {
	var w myInterface
	w = myStruct{}
	fmt.Fprintf(w, "Go rocks")
}

Beispiele für Implementierung durch Vererbung

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 erbt und implementiert das ReadWriter-Interface die Eigenschaften des Writer-Interfaces,
	              // da "Stdout" (Variable des Packages "os") ein Zeiger auf Datentyp "File" ist, der in 
	              // Package "os" deklariert ist. Read() und Write() sind Methoden des Datentyps 
	              // "os.File" und erfüllen somit die Vorgaben der Interfaces.
	              // 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")
}

Type assertions

package main

import "fmt"

func main() {
	var t interface{}
	t = 123
	a := t.(int)
	fmt.Println(a) // 123

	/*Type assertions liefern im Erfolgsfall als zweiten Parameter "true" zurück, ansonsten "false"*/
	ai, ok := t.(int)
	fmt.Println(ai, ok) // 123 true
	af, ok := t.(float64)
	fmt.Println(af, ok) // 0 false

	// Expemple: Type assertion innerhalb eines if-else-Blockes als Type-Prüfung
	var b interface{}
	// Probiere beide Zuweisungen aus:
	//b = "Blabla"
	b = 123
	if str, ok := b.(string); ok {
		fmt.Printf("Der String b enthält den Wert: %s\n", str)
	} else {
		fmt.Println("b ist kein String")
	}
}

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.(type) {
	switch u := t.(type) { // dasselbe mit Wertzuweisung
	default:
		fmt.Printf("unexpected type %T\n", u)
	case bool:
		fmt.Printf("boolean %t\n", u)
	case int:
		fmt.Printf("integer %d\n", u)
	case float64:
		fmt.Printf("float64 %g\n", u)
	case *bool:
		fmt.Printf("pointer to boolean %t\n", *u)
	case *int:
		fmt.Printf("pointer to integer %d\n", *u)
	case *float64:
		fmt.Printf("pointer to float64 %g\n", *u)
	case string:
		fmt.Printf("string %s\n", u)
	}
}

Anmerkung
In type switches ist das Statement fallthrough unzulässig

Anwendung: 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
	fmt.Printf("%s = %s\n", NumbOhne{"XC"}, NumbOhne{90}) // XC = 90
	fmt.Println(Numb{123.456})                            // Unzulässiger Typ: float64
}

Anwendung: 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
}

Polymorphie

... wird mit Interfaces möglich, da Interfaces zwar die "Methoden" (Funktionen) vorschreiben, die Gestalt der "Attribute" (Variablen) aber völlig offen lassen.

package main

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

type confuser interface {
	shuffle(time.Time) string
}

type numerals struct {
	n int
}

type words struct {
	w string
}

func (r numerals) shuffle(t time.Time) string {
	defer fmt.Println(t)

	numerallist := []rune(strconv.Itoa(r.n))

	rand.Seed(time.Now().Unix())
	rand.Shuffle(len(numerallist), func(i, j int) { numerallist[i], numerallist[j] = numerallist[j], numerallist[i] })

	return string(numerallist)
}

func (r words) shuffle(t time.Time) string {
	defer fmt.Println(t)

	wordlist := strings.Split(r.w, " ")

	rand.Seed(time.Now().Unix())
	rand.Shuffle(len(wordlist), func(i, j int) { wordlist[i], wordlist[j] = wordlist[j], wordlist[i] })

	return strings.Join(wordlist, " ")
}

func letsrock(c confuser) {
	fmt.Println(c)
	fmt.Println(c.shuffle(time.Now()))
}

func main() {
	inputn := numerals{1234567890}
	inputw := words{"penguin Tux eats fish"}

	letsrock(inputn) /*
		{1234567890}
		2020-09-26 20:59:38.419783812 +0200 CEST m=+0.000065019
		3265847091
	*/
	letsrock(inputw) /*
		{penguin Tux eats fish}
		2020-09-26 21:18:38.019358421 +0200 CEST m=+0.000169138
		Tux eats penguin fish
	*/
}

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. Existieren mehrere defer-Funktionsaufrufe, werden diese nacheinander in umgekehrter Reihenfolge (Last In First Out) ausgeführt, nachdem die Funktion endete.
panic(interface{})
Eine Build-In-Funktion zur sofortigen Unterbrechung der Funktionsauführung mit Fehlermeldung.
recover() interface{}
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() { // 7.
		if err := recover(); err != nil { // Da der error zuvor schon abgefangen wurde und nun nicht mehr besteht, ...
			fmt.Println("Wird nicht ausgegeben, da der Fehler zuvor schon abgefangen worden war!") // ... wird die Ausführung dieser Code-Zeile verhindert.
		}
	}()
	defer fmt.Println("Hier steht auch noch was!") // 6.
	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!") // 8.
}

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

Synchronisation über WaitGroups

Eine fest vordefinierte Wartezeit ist natürlich nicht sinnvoll. Besser ist es, auf die Ausführung der einzelnen Subroutinen zu warten.

package main

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

var wg sync.WaitGroup

func say(s string, proc int) {
	defer wg.Done()
	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.
	wg.Add(6)
	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)
	wg.Wait() // Anstelle der Wartezeit, warten wir besser auf das Ende der letzten Funktion

	fmt.Println("Puuuh!")
}

Synchronisation über den wechselseitigen Ausschluß ("Mutex")

Ein Mutex (Abk. für mutual exclusion) erlaubt das Setzen und Freigeben von Sperren

package main

import (
	"fmt"
	"sync"
	"time"
)

var mtx sync.Mutex

func doSomething() {
	mtx.Lock()
	for i := 0; i <= 1000; i++ {
		fmt.Println("DoSomething", i)
	}
	mtx.Unlock()
}

func doSomethingElse() {
	defer mtx.Unlock()

	mtx.Lock()
	for i := 0; i <= 1000; i++ {
		fmt.Println("DoSomethingElse", i)
	}
}

func main() {

	// Das Lock verhindert die Abarbeitung der zweiten Funktion, bevor die erste
	// ihre Aufgabe beendet hat.
	// Ohne das Lock würden beide Funktionen ihre Textausgaben zeitgleich vermischen.
	go doSomething()
	go doSomethingElse()

	time.Sleep(3 * time.Second)
}

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)      // Ungepufferter Channel. 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.
Anmerkung
Wenn in einen Channel geschrieben wird, bleibt der Programmzeiger solange an dieser Stelle stehen, bis aus dem Channel wieder gelesen wurde. Dies kann umgangen werden, wenn das Schreiben nebenläufig von einer anonymen Funktion übernommen wird (go func(){mychannel <- 123}()).
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, ...
	stop <- true                                                                                                       // ... und beende auch alle anderen Routinen
}

func say(c chan string, stop chan bool) {
	for {
		select {
		case cvalue, cstatus := <-c:
			if cstatus {
				fmt.Print(cvalue) // Lese aus Channel
			} 
		case <-stop:
			return
		default:
			time.Sleep(50 * time.Millisecond)
		}
	}
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ChanelNo5 := make(chan string, 1)
	ChanelNo19 := make(chan bool, 6) // 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(ChanelNo5, ChanelNo19)

	fmt.Println("Puuuh!")
	close(ChanelNo5) // Channels können wieder geschlossen werden, wenn sie nicht mehr benötigt werden
	close(ChanelNo19)
}

Channel-Puffer prüfen

package main

import "fmt"

var pipe chan int

func main() {
	pipe = make(chan int, 3)

	pipe <- 123
	pipe <- 456

	fmt.Println(cap(pipe)) // 3
	fmt.Println(len(pipe)) // 2
	fmt.Println(<-pipe)    // 123
	fmt.Println(len(pipe)) // 1
	fmt.Println(<-pipe)    // 456
	fmt.Println(len(pipe)) // 0
	fmt.Println(cap(pipe)) // 3
}

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

oder:

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

Der context-Datentyp

Der Datentyp context kann Zeitpunkte/Fristen, Cancel-Signale oder auch Vergleichswerte auf mehrere Routinen verteilen. Er ist mit geringerem Aufwand als Signal-Bus einzusetzen, als gepufferte Channels.

package main 

import (
	"context"
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

const (
	rightString = "Oh Yeah"
	wrongString = "D'oh"
)

func doSth1(ctx context.Context) {
	defer wg.Done()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("just in time")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}

func doSth2(ctx context.Context, k string) {
	defer wg.Done()

	if v := ctx.Value(k); v != nil {
		fmt.Println("found value:", v)
		return
	}
	fmt.Println("key not found:", k)
}

func main() {
	fmt.Println("Context test 1 - Deadline:")
	d := time.Now().Add(time.Duration(3000 * time.Millisecond))
	ctx, cancel := context.WithDeadline(context.Background(), d)
	defer cancel()

	wg.Add(6)
	go doSth1(ctx)
	go doSth1(ctx)
	go doSth1(ctx)
	time.Sleep(3 * time.Second)
	go doSth1(ctx)
	go doSth1(ctx)
	go doSth1(ctx)
	wg.Wait()

	// Context test 1 - Deadline:
	// just in time
	// just in time
	// just in time
	// context deadline exceeded
	// context deadline exceeded
	// context deadline exceeded

	fmt.Println("\nContext test 2 - Value:")
	ctx = context.WithValue(context.Background(), "Oh Yeah", rightString)

	wg.Add(8)
	go doSth2(ctx, rightString)
	go doSth2(ctx, rightString)
	go doSth2(ctx, rightString)
	go doSth2(ctx, rightString)
	go doSth2(ctx, wrongString)
	go doSth2(ctx, wrongString)
	go doSth2(ctx, wrongString)
	go doSth2(ctx, wrongString)
	wg.Wait()

	// Context test 2 - Value:
	// key not found: D'oh
	// found value: Oh Yeah
	// found value: Oh Yeah
	// found value: Oh Yeah
	// key not found: D'oh
	// key not found: D'oh
	// key not found: D'oh
	// found value: Oh Yeah
}

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
=== 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
Tipp: Unsystematische Laufzeit-Messung von Codesegmenten
package main

import (
	"log"
	"math"
	"time"
)

func main() {
	zeitpunkt := time.Now()
	a := math.Sqrt(123456)
	log.Println(time.Since(zeitpunkt)) //2018/11/07 20:30:56 152ns
	_ = a
}

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

Kontrollieren 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

Reflections – Bezüge auf den eigenen Code

package main

import (
	"fmt"
	"reflect"
)

type einObjektTyp struct {
	Maja  int
	willi float32
	flip  bool
	Max   string
}

var motz int
var Rempel int16

func main() {
	var einObjekt einObjektTyp = einObjektTyp{123, 456.789, true, "einszweidrei"}
	fmt.Println(reflect.ValueOf(einObjekt)) // {123 456.789 true einszweidrei}
	fmt.Println(reflect.TypeOf(einObjekt))  // main.einObjektTyp

	fmt.Println(reflect.ValueOf(einObjekt).Field(1))            // 456.789
	fmt.Println(reflect.TypeOf(einObjekt).FieldByName("willi")) // {willi main float32  8 [1] false} true

	fmt.Println(reflect.TypeOf(einObjekt).Field(2).Type)   // bool
	fmt.Println(reflect.TypeOf(einObjekt).Field(2).Index)  // [2]
	fmt.Println(reflect.TypeOf(einObjekt).Field(2).Name)   // flip
	fmt.Println(reflect.TypeOf(einObjekt).Field(2).Offset) // 12, weil 8 (int) + 4 (float32) = 12

	fmt.Println(reflect.TypeOf(einObjekt).Field(3).Offset) // 16, weil 8 (int) + 4 (float32) + 4 (bool) = 16

	fmt.Println(reflect.ValueOf(&einObjekt).Elem().Field(3)) // einszweidrei

	// Variablen können mit Hilfe eines Pointers beschreiben werden! Felder einer Struktur müssen jedoch exportiert werden.
	fmt.Println(reflect.ValueOf(&einObjekt).Elem().Field(3).CanSet()) // true, weil "Max" exportiert wird (public)
	fmt.Println(reflect.ValueOf(&einObjekt).Elem().Field(1).CanSet()) // false, weil "willi" nicht exportiert wird (private)
	reflect.ValueOf(&einObjekt).Elem().Field(3).SetString("eintausendundzwei")
	//reflect.ValueOf(&einObjekt).Elem().Field(1).SetFloat(1.2345) wäre deshalb unzulässig!

	fmt.Println(reflect.ValueOf(&motz).Elem().CanSet())   // true, da "motz" eine Variable ist und nicht Feld einer Struktur!
	fmt.Println(reflect.ValueOf(&Rempel).Elem().CanSet()) // true

	// Bei Verwendung des typunabhängigen Set(x reflect.Value) muß auch das aus der Struktur gelesene Feld exportierbar sein!
	reflect.ValueOf(&motz).Elem().Set(reflect.ValueOf(einObjekt).Field(0))
}

Iteration durch Strukturen

  • Da nicht mit range durch Strukturen iteriert werden kann, hilft hier das reflect-Package:
package main

import (
	"fmt"
	"reflect"
)

type einObjektTyp struct {
	Maja  int
	willi float32
	flip  bool
	Max   string
}

func main() {
	var einObjekt einObjektTyp = einObjektTyp{123, 456.789, true, "einszweidrei"}

	for i, rType, rValue := 0, reflect.TypeOf(&einObjekt).Elem(), reflect.ValueOf(&einObjekt).Elem(); i < rType.NumField(); i++ {
		fmt.Println(rType.Field(i), rValue.Field(i)) /*
					{Maja  int  0 [0] false} 123
			 		{willi main float32  8 [1] false} 456.789
				    {flip main bool  12 [2] false} true
					{Max  string  16 [3] false} einszweidrei
		*/
	}
}

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"
	"unsafe"
)

// #cgo CFLAGS: -O2 -march=native
// #cgo LDFLAGS: -lm
// #include <math.h>
// #include <stdio.h>
// #include <stdlib.h>
// #include <ctype.h>
/*

double mySqrt(int mydata)
{
	return sqrt(mydata);
}

struct
{
  int tag;
  char monat[10];
  int jahr;
  char *anlass;
} termin1;

struct anonym
{
  int tag;
  char monat[10];
  int jahr;
  char *anlass;
};
*/
import "C"

const steps int64 = 100000000

var (
	a  float64
	b  C.double
	pg *float64  // Zeiger in Go
	pc *C.double // Zeiger in C
	sg string    // String in Go
	sc *C.char   // String in C
	bg []byte    // Slice of Bytes in Go
	ag [5]int32  // Int-Array in Go
	ac [5]C.int  // Int-Array in C
)

func main() {
	// Verwende math aus den Go-Packages
	t0 := time.Now()
	for i := (int64)(1); i <= steps; i++ {
		a = Go.Sqrt(float64(i))
	}
	fmt.Println(time.Since(t0)) // Ca. 170 ms Ausführungszeit der nativen Funktion aus dem Go-math-Package ist signifikant schneller ...

	// Verwende math aus den C-Packages
	t1 := time.Now()
	for i := (C.int)(1); i <= C.int(steps); i++ {
		b = C.sqrt(C.double(i))
	}
	fmt.Println(time.Since(t1)) // ... als bei Verwendung der math.h; ca. 5s

	// Schreibe eine eigene C-Funktion
	t2 := time.Now()
	for i := (C.int)(1); i <= C.int(steps); i++ {
		b = C.mySqrt(i)
	}
	fmt.Println(time.Since(t2)) // Ca. 5s

	// Zeiger funktioneren in CGO auf dieselbe Weise
	pc = &b              // Adresse aus dem C-Adressraum
	pg = (*float64)(&b)  // Umwandlung eines C-Pointers in einen Go-Pointer
	fmt.Println(pc, pg)  // Beide Adressen sind identisch (z.B. 0x77f3c8 0x77f3c8)
	pc = (*C.double)(pg) // Umwandlung eines Go-Pointers in einen C-Pointer
	fmt.Println(pg)      // z.B. 0x77f3c8

	// String-Verarbeitung
	sg = "abcdefghijklmnopqrstuvwxyz"
	sc = C.CString(sg)              // Wandele Go-String in C-String
	fmt.Println(C.GoString(sc))     // Wandele C-String in Go-String (abcdefghijklmnopqrstuvwxyz)
	fmt.Println(C.GoStringN(sc, 5)) // Wandele C-String mit definierter Länge in Go-String (abcde)

	// Byte-Verarbeitung
	bg = C.GoBytes(unsafe.Pointer(sc), 26) // Die Umwandlung eines Byte-Arrays von C nach Go erfordert einen unsafe.Pointer
	fmt.Println(bg)                        // [97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122]
	bc := *(*[26]byte)(C.CBytes(bg))       // bg ist ein unsafe.Pointer auf den Beginn der Bytefolge. Wandele ihn in einen sicheren Pointer des Datentyps []byte um und gebe dessen Inhalt an bc aus. Wichtig ist hier die Längenangabe des Arrays, da C keine Slices kennt!
	fmt.Println(bc)                        // [97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122]

	// Memory-Management
	defer C.free(unsafe.Pointer(sc)) // In C gibt es keinen Garbage-Collector, wir müssen uns selber darum kümmern, den (mit malloc(),
	// calloc() oder realloc()) alloziierten Speicherbereich wieder freizugeben, so wie es bei char pointern auf Zeichenketten
	// unbestimmter Länge durch cgo beim kompillieren geschieht

	// Arrays/Slices - werden über Zeiger auf ihre Startadressen ausgetauscht. Grundsätzlich ist zu beachten, daß der Zeiger auf das erste Element eines Arrays/Slices zeigt: "&slice[0]" und nicht "&slice" !
	ag = [5]int32{1, 2, 3, 4, 5} // Achtung! Auf die richtige Datenlänge muß geachtet werden C.int entspricht int32 in Go!
	ac = *(*[5]C.int)(unsafe.Pointer(&ag))
	fmt.Println(ag, ac) // [1 2 3 4 5] [1 2 3 4 5] Bei int64 wäre das Ergebnis der Umwandlung [1 2 3 4 5] [1 0 2 0 3]

	// Zugriff auf benannte C-Struktur
	C.termin1.tag = 24
	C.termin1.monat = *(*[10]C.char)(unsafe.Pointer(&[10]byte{'D', 'e', 'z', 'e', 'm', 'b', 'e', 'r'}))
	C.termin1.jahr = 2018
	C.termin1.anlass = C.CString("Weihnachten")
	fmt.Println(C.termin1) // {24 [68 101 122 101 109 98 101 114 0 0] [0 0] 2018 [0 0 0 0] 0x14d9840}

	// Zugriff auf unbenannte C-Struktur über das optionale structure tag
	var termin2 C.struct_anonym //Beachte den Prefix "C.struct_" vor dem structure tag, ohne den die anonyme Struktur nicht adressierbar wäre!
	termin2.tag = 24
	termin2.monat = *(*[10]C.char)(unsafe.Pointer(&[10]byte{'D', 'e', 'z', 'e', 'm', 'b', 'e', 'r'}))
	termin2.jahr = 2018
	termin2.anlass = C.CString("Weihnachten")
	fmt.Println(termin2) // {24 [68 101 122 101 109 98 101 114 0 0] [0 0] 2018 [0 0 0 0] 0x14d9860}
}

Referenzierung von C zu Go

In der Regel werden zwei Go-Code-Files benötigt, da CGO zwei C-Output-Files der exportierten Funktionen erzeugt und dort ansonsten alle C-Definitionen unzulässig doppelt existierten.

//File 1 von 2
package main

/*
#cgo CFLAGS: -O2 -march=native

extern void ccallsme(char *);

void callback2go(char *text)
{
  ccallsme(text);
}
*/
import "C"

func main() {
	C.callback2go(C.CString("Ich bin eine Go-Funktion wurde von einer C-Funktion aufgerufen"))
}

Der zur Annotation erweiterte Kommentar //export ist notwendig, damit CGO die Go-Funktion im C-Code referenzieren kann:

//File 2 von 2
package main

//#cgo CFLAGS: -O2 -march=native
import "C"

import "fmt"

//export ccallsme
func ccallsme(text *C.char) {
	fmt.Println(C.GoString(text))
}

Siehe https://golang.org/cmd/cgo/[13]

Tipp
Sehr hilfreich bei der Arbeit mit cgo ist die Verwendung von go tool cgo main.go. Es erzeugt im Projektverzeichnis einen Order _obj mit den von Compiler generierten Objekt-Codes. Beispielsweise die Datei _obj/_cgo_gotypes.go hilft beim nicht ganz einfachen Type-Casting zwischen C und Go.
Noch ein Tipp
Nebenläufigkeit mit cgo verwendenden Routinen kann insbesondere bei der Verwendung externer C-Libraries zu unvorhergesehenen Speicherzugriffsfehlern führen, die im einzeln ausgeführten Prozess nicht auffallen. In diesem Fall kann die Flankierung von cgo-Codes mit (global definierten!) Mutexes (sync.Mutex.Lock() vorher und sync.Mutex.Unlock() hinterher) für Stabilität sorgen.

GUI

Verteilte Systeme

IDE

  • Die universelle Entwicklungsumgebung Visual Studio Code von Microsoft ist so gut, daß ich sie selbst als eingefleischter Linux-Freak empfehle! Sie ist derzeit meine erste Wahl für Projekte mit unterschiedlichen Code-Formaten.
  • Mein absoluter Favorit speziell für Go ist jedoch die LiteIDE. Sie ist wirklich "leicht", jedoch erstaunlich mächtig und ausgereift. Alles ist auf kurzem Wege intuitiv erreichbar und geht fühlbar zügiger als bei VSCode, grobe Bugs sucht man vergebens. Das Layout ist sehr übersichtlich und durchdacht. Lediglich eine GIT-Sourcecode-Verwaltung vermisse ich.

Links

Benchmark

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

Kritik

Anmerkungen

  1. Vgl. Sakshi Sharma: "Programming With Go (GOlang) And It’s Benefits", December 18, 2017
  2. 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
    Die objektiven Gegenüberstellungen "Rust vs Go in 2023" von John Arundel, "Go vs Rust? Choose Go." von Matthias Endler (15.9.2017) und "Should I Rust, or Should I Go" von Andrew Lader (26.10.2017) sind sehr empfehlenswert und machen neugierig auch auf Rust.
  3. 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)
    Über weitere Vorzüge der Programmiersprache Go schreiben Kirill Rogovoy: "Here are some amazing advantages of Go that you don’t hear much about", February 1, 2018 und Sugandha Lahoti: "Why Golang is the fastest growing language on GitHub", August 9, 2018
  4. https://jaxenter.de/5-dinge-go-golang-hass-69789
  5. 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. [...]"
  6. Siehe hierzu und auch zu GOPROXY https://golang.org/cmd/go/#hdr-Module_configuration_for_non_public_modules
  7. Siehe https://blog.boot.dev/golang/how-to-use-golangs-generics/
  8. Vergleiche Interface und Pointer-Receiver
  9. Neuere Implementierungen überschreiben vorherige!
  10. Adressiere die Felder stattdessen über "Getter"- und "Setter"-Methoden.
    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/)
  11. Siehe auch Abschnitt Composite Types mittels Interface und Struct und ausführlichen Post zum Thema: http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go
  12. Vergleiche Wo Pointer-Receiver notwendig sind
  13. Zur weiterern Vertiefung: https://dave.cheney.net/2016/01/18/cgo-is-not-go
  14. Auf Basis von BenchmarkPzf.go, BenchmarkPzf.py und BenchmarkPzf.java