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

Aus archium
Wechseln zu:Navigation, Suche

Inhaltsverzeichnis

Warum Go? Achtung - subjektiv!

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

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

Compiler

Gc

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

Erzeuge ein statisches Binary

$ go build byebyeJava.go
$ ./byebyeJava

Führe das "Script" sofort aus

$ go run byebyeJava.go

Kompiliere ein externes Package

$ go install [packages]

Lade externe Packages aus einem Repository

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

Cross-Compiling

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

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

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

Einblick in den Assembler-Code

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

$ go build -gcflags -S byebyeJava.go

Gccgo

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

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

Packages

Verzeichnisstruktur

Vorbereitung des Workspace

$ export GOPATH='/home/me/workspace'

Verzeichnisse, die Go im Workspace anlegt

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

Packages von außen

Import

package main

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

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

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

Packages von innen

Benennung

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

Sichtbarkeit von Variablen und Funktionen

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

Standard-Funktionen

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

Funktionen

Einfache Funktionen

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

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

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

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

var x, y = functionName2()

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

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

var x, y = functionName2()

Veränderliche Parameter (Variadic Functions)

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

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

func adder(args ...int) int {
	total := 0
	for _, v := range args { // Iterates over the arguments whatever the number.
		total += v
	}
	return total
}

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

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

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

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

Funktionen mit Gedächtnis: Closures

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

import (
	"fmt"
)

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

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

	fmt.Printf("%v %v %v\n", c(), c(), c()) // => 1 2 3

}

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

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

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

Speicherverwaltung

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

Deklarationen

Variablen

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

Konstanten

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

Zeiger (Pointer)

Speicher-Allozierung durch Pointer auf Variable
var a int
var b *int // Pointer auf int
var c *float64 // Pointer auf float64
a = 2
b = &a // b ist ein Pointer und bekommt die Adresse von a
*b = 3 // Die Wert an der Adresse, auf die b zeigt, bekommt einen Wert zugewiesen. (Somit ist nun auch a = 3)
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)
Anmerkung
Zeigerarithmetik (wie in C) gibt es in Go nicht.

Typumwandlung

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

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

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

Arrays, Slices, Strings, Maps, Ranges

Arrays

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

Slices

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

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

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

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

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

package main

import "fmt"

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

führt zu folgendem Ergebnis:

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

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

Strings

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

"Bye bye Java"[8:10] == "Ja"
"Bye bye Java"[8] == 'J' 
string("Bye bye Java"[8]) == "J"

Maps

Sämtliche Types können einer Map zugeordnet werden

package main

import "fmt"

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

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

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

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

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

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

Ranges

Iteration durch die Argumente von Array, Slice oder Map

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

Built-in Types

default: false

bool

default: ""

string

default: 0

int int8 int16 int32 int64 // int entspricht – von der Systemarchitektur abhängig – int32 oder int64
uint uint8 uint16 uint32 uint64 uintptr // uint entspricht – von der Systemarchitektur abhängig – uint32 oder uint64
byte // Alias für uint8
rune // Alias für int32, repräsentiert einen Unicode Code Point
uintptr  // unsigned integer, groß genug um das Bitmuster jedes anderen Pointers zu speichern. Siehe https://golang.org/pkg/unsafe/

default: 0.0

float32 float64
complex64 complex128

default: nil

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

Strukturen

package main

import "fmt"

type programmingLanguages struct {
	name   string
	rating int
	future bool
}

func main() {

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

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

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

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

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

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

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

Operatoren

Arithmetic

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

Comparison

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

Logical

&& logical and
|| logical or
! logical not

Other

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

Kontrollstrukturen

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

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

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

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

Bedingungen

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

Schleifen

  • Es gibt nur ein sehr flexibles for, so daß keine Notwendigkeit für ein while mehr besteht.
for i := 1; i < 10; i++ {
}

for ; i < 10; { // while - loop
}

for i < 10 { // die Semikolons können weggelassen werden
}

for { // ohne Bedingung entspricht einem while (true)
}

Switch

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

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

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

Sichtbarkeit von Variablen in Kontrollstrukturen

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

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

Ebenfalls gültig:

package main

import (
	"fmt"
)

var i int = 123

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

Geächtet, aber möglich: goto

package main

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

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

Objektorientierung

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

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

Vererbung

package main

import "fmt"

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

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

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

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

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

import "fmt"

type pseudoClass int //  Deklaration einer Klasse mit den Eigenschaften des Datentyps "int"

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

func main() {

	newPseudoInstanz := pseudoClass(2) // In diesem Fall hat der Datentyp dann auch einen Konstruktor
	fmt.Println(newPseudoInstanz.rechenMethode())
	fmt.Println(newPseudoInstanz.rechenMethodeMitArgument(3))
}

Interfaces

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

import (
	"fmt"
	"os"
)

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

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

type ReadWriter interface {
	Reader
	Writer
}

func main() {
	var w ReadWriter

	w = os.Stdout // Hier wird das ReaderWriter-Interface implementiert, da Read und Write in Package os definiert sind
	fmt.Fprintf(w, "Cooler als eine Java ProblemFactory!\n")
}

Polymorphie

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

import "fmt"

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

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

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

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

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

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

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

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

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

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

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

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

Interface als Universaltyp

  • Noch eine Eigenschaft: Der leere Interfacetyp kann zur Definition von Variablen beliebigen Typs verwendet werden ("Unboxing").
package main

import (
	"fmt"
)

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

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

Type assertions

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

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

Type switches

package main

import (
	"fmt"
)

var t interface{}

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

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

Error-Handling

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

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

type ErrorMessage1 struct {
	Zeitpunkt time.Time
	Meldung   string
}

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

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

type ErrorMessage2 string

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

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

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

func main() {
	if err := doFirstError(); err != nil {
		fmt.Println(doFirstError())
	}
	if err := doSecondError(); err != nil {
		fmt.Println(err)
	}
	if err := doThirdError(); err != nil {
		fmt.Println(err)
	}
}
  • Abfangen von Fehlern
package main

import "fmt"

func main() {
	defer func() {
		fmt.Println("Der Fehler war:", recover())
	}()
	panic("Werfe kritischen Fehler")
}

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) {
	for i := 0; i < 5; i++ {
		time.Sleep(time.Duration(rand.Intn(10000)) * time.Millisecond) // Der Zufallsgenerator macht die unterschiedliche Ausführungsdauer der einzelnen Unterprogramme noch besser sichtbar.
		fmt.Printf("Prozess=%v, Schleife=%v, Wert=%v\n", proc, i, s)
	}
	fmt.Printf("\nProzess %v ist fertig!\n\n", proc)
}

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

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

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

Kommunikation zwischen Routinen

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

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

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

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

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

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

Das selektive Warten auf bestimmte Channel-Aktivitäten

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

package main

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

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

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

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

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

go test

Konvention bei Datei- und Funktionsnamen

Referenz-Code

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

import (
	"fmt"
	"math"
)

var input float64

func main() {

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

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

Testcode

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

import (
	"math"
	"testing"
)

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

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

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

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

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

Ausführung

Unittests

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

Benchmarks

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

Profiling

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

import (
	"runtime"
	"runtime/debug"
)

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

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

C code einbinden

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

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

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

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

const steps int64 = 100000000

var a float64
var b C.double

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

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

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

IDE

  • Leicht aber erstaunlich mächtig, ausgereift und die momentan (Ende 2016) vielleicht beste Go-IDE überhaupt: liteIDE

Links

Benchmark

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

Anmerkungen

  1. 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. [...]"
  2. Auf Basis von BenchmarkPzf.go, BenchmarkPzf.py und BenchmarkPzf.java