Add German translation of chapter 2

This commit is contained in:
digitalcraftsman
2015-08-07 22:53:30 +02:00
parent 681dfb594c
commit 0441fbce21
9 changed files with 1429 additions and 1412 deletions

View File

@@ -1,17 +1,17 @@
# 2 Go basic knowledge
# 2 Grundlegendes Wissen zu Go
Go is a compiled system programming language, and it belongs to the C-family. However, its compilation speed is much faster than other C-family languages. It has only 25 keywords... even less than the 26 letters of the English alphabet! Let's take a look at these keywords before we get started.
Go ist eine kompilierte, systemnahe Programmiersprache und ist verwandt mit der C-Familie. Die Kompiliereung ist deutlich schneller als bei anderen C-Sprachen. Sie besitzt nur 25 Schlüsselwörter... also noch weniger als die 26 Buchstaben des deutschen Alphabets! Lass uns einen kurzen Blick auf diese Schlüsselwörter werfen, bevor wir anfangen.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
In this chapter, I'm going to teach you some basic Go knowledge. You will find out how concise the Go programming language is, and the beautiful design of the language. Programming can be very fun in Go. After we complete this chapter, you'll be familiar with the above keywords.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
In diesem Kapitel werde ich Dir Grundlagen zur Programmierung in Go vermitteln. Du wirst sehen, wie prägnant die Sprache ist und wie schön sie gestaltet wurde. Das Programmieren in Go kann viel Spaß machen. Am Ende dieses Kapitels werden Dir die aufgelisteten Schlüsselwörter in ihrer Funktionsweise bekannt sein.
## Links
- [Directory](preface.md)
- Previous chapter: [Chapter 1 Summary](01.5.md)
- Next section: ["Hello, Go"](02.1.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriges Kapitel: [Kapitel 1 Zusammenfassung](01.5.md)
- Nächster Abschnitt: ["Hallo Go"](02.1.md)

View File

@@ -1,53 +1,53 @@
# 2.1 Hello, Go
# 2.1 Hallo Go
Before we start building an application in Go, we need to learn how to write a simple program. You can't expect to build a building without first knowing how to build its foundation. Therefore, we are going to learn the basic syntax to run some simple programs in this section.
Bevor wir unsere erste Anwendung in Go erstellen, müssen wir erst klein anfangen. Du kannst nicht erwarten, ein Haus bauen zu können, ohne die Grundlagen zu beherrschen. Deshalb werden wir den grundlegenden Syntax in diesem Abschnitt lernen, um ein einfaches Programm zu schreiben.
## Program
## Programm
According to international practice, before you learn how to program in some languages, you will want to know how to write a program to print "Hello world".
In der Programmierung ist es in Klassiker, als erstes eine Anwendung zu schreiben, die einzig "Hallo Welt" ausgibt.
Are you ready? Let's Go!
Bist Du bereit? Los geht's!
package main
import "fmt"
func main() {
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界\n")
fmt.Printf("Hallo Welt oder 你好,世界 oder καλημ ́ρα κóσμ oder こんにちは世界\n")
}
It prints following information.
Es sollte jetzt folgendes ausgegeben worden sein.
Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界
Hallo Welt oder 你好,世界 oder καλημ ́ρα κóσμ oder こんにちは世界
## Explanation
## Erklärung
One thing that you should know in the first is that Go programs are composed by `package`.
Eine Sache, die Du zu Beginn wissen solltest, ist, dass Go-Programme stets mit dem Schlüsselwort `package` anfangen.
`package <pkgName>` (In this case is `package main`) tells us this source file belongs to `main` package, and the keyword `main` tells us this package will be compiled to a program instead of package files whose extensions are `.a`.
`package <Paketname>` (in diesem Fall `package main`) sagt aus, dass diese Quellcodedatei zum Paket `main` gehört. Das Schlüsselwort `main` ist jedoch besonders, da es zu einer ausführbaren Binärdatei kompiliert wird. Normalerweise wird sonst ein Paket in der Dateiendung `.a` generiert.
Every executable program has one and only one `main` package, and you need an entry function called `main` without any arguments or return values in the `main` package.
Jedes ausführbare Programm hat nur ein einziges `main` Paket, dass die Funktion `main` beinhalten muss, die keine Argumente erhält und keine Werte zurückgibt.
In order to print `Hello, world…`, we called a function called `Printf`. This function is coming from `fmt` package, so we import this package in the third line of source code, which is `import "fmt"`
Um `Hallo Welt...` auszugeben, haben wir eine Funktion mit dem Namen `Printf` genutzt. Diese Funktion stammt aus dem `fmt` Paket, welches wir in der dritten Zeile mit `import "fmt"` importiert haben.
The way to think about packages in Go is similar to Python, and there are some advantages: Modularity (break up your program into many modules) and reusability (every module can be reused in many programs). We just talked about concepts regarding packages, and we will make our own packages later.
Pakete werden ähnlich wie in Python behandelt, was ein paar Vorteile mit sich bring: Modularität (das Aufteilen eines Programms in kleinere Codesegmente, genannt Module) und Wiederverwendbarkeit (viele Module können auch in anderen Programmen genutzt werden). Das Konzept um Pakete hatten wir bereits aufgegriffen. Später werden wir auch eigene Pakete erstellen.
On the fifth line, we use the keyword `func` to define the `main` function. The body of the function is inside of `{}`, just like C, C++ and Java.
In der fünften Zeile haben wir das Schlüsselwort `func` verwendet, um die `main`-Funktion zu definieren. Der Rumpf bzw. Körper einer Funktion befindet sich zwischen `{}`, genau wie in C, C++ und Java.
As you can see, there are no arguments. We will learn how to write functions with arguments in just a second, and you can also have functions that have no return value or have several return values.
Wie Du siehst, gibt es keine Argumente, die an die Funktion übergeben worden sind. Wir werden in wenigen Sekunden lernen, wie man diese in Funktionen nutzt. Du kannst auch Funktionen schreiben, die keinen oder beliebig viele Rückgabewerte haben.
On the sixth line, we called the function `Printf` which is from the package `fmt`. This was called by the syntax `<pkgName>.<funcName>`, which is very like Python-style.
In der sechsten Zeile rufen wir die Funktion `Printf` aus dem Paket `fmt` auf. Die geschieht über den Syntax `<Paketname>.<Funktionsname>`, also ganz im Stil von Python.
As we mentioned in chapter 1, the package's name and the name of the folder that contains that package can be different. Here the `<pkgName>` comes from the name in `package <pkgName>`, not the folder's name.
Wie in Kapitel 1 angemerkt, können sich der Name des Pakets und der Ordner, indem es sich das Paket befindet, durchaus verschieden sein. Hier stammt der `<Paketname>` vom Namen in `package <Paketname>` und nicht vom Ordnernamen ab.
You may notice that the example above contains many non-ASCII characters. The purpose of showing this is to tell you that Go supports UTF-8 by default. You can use any UTF-8 character in your programs.
Du hast vielleicht mitbekommen, dass unser Beispiel oben viele Nicht-ASCII-Zeichen beinhaltet. Dies hat den Zweck zu zeigen, dass Go UTF-8 standardmäßig unterstützt. Du kannst jedes beliebige UTF-8 Zeichen in Deinen Programmen verwenden.
## Conclusion
## Zusammenfassung
Go uses `package` (like modules in Python) to organize programs. The function `main.main()` (this function must be in the `main` package) is the entry point of any program. Go supports UTF-8 characters because one of the creators of Go is a creator of UTF-8, so Go has supported multiple languages from the time it was born.
Go nutzt `package` (wie Module in Python) zum Strukturieren von Programmen. Die Funktion `main.main()` (diese Funktion muss sich im `main`-Paket befinden) ist der Startpunkt von jedem Programm. Go unterstützt UTF-8-Zeichen, da einer ihrer Schöpfer zugleich UTF-8 mitentwickelt hat. Somit unterstützt Go von Beginn an mehrere Sprachen.
## Links
- [Directory](preface.md)
- Previous section: [Go basic knowledge](02.0.md)
- Next section: [Go foundation](02.2.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Grundlegendes Wissen zu Go](02.0.md)
- Nächster Abschnitt: [Grundlagen von Go](02.2.md)

View File

@@ -1,57 +1,58 @@
# 2.2 Go foundation
# 2.2 Grundlagen von Go
In this section, we are going to teach you how to define constants, variables with elementary types and some skills in Go programming.
In diesem Abschnit werden wir lernen, wie man Konstanten und Variablen mit grundlegenden Datentypen definiert, sowie ein paar weitere Fähigkeiten aus der Go-Programmierung.
## Define variables
## Variablen definieren
There are many forms of syntax that can be used to define variables in Go.
Es gibt syntaktisch viele Wege, eine Variable in Go zu definieren.
The keyword `var` is the basic form to define variables, notice that Go puts the variable type `after` the variable name.
Das Schlüsselwort `var` ist die simpelste Form, eine Variable zu erstellen. Bedenke, dass in Go der Datentyp **hinter** dem Variablennamen steht.
// define a variable with name “variableName” and type "type"
// Definiere eine Variable mit dem Namen “variableName” vom Typ "type"
var variableName type
Define multiple variables.
Definiere mehrere Variablen.
// define three variables which types are "type"
// Definiere drei Variablen vom Typ "type"
var vname1, vname2, vname3 type
Define a variable with initial value.
Definiere eine Variable mit einem Startwert.
// define a variable with name “variableName”, type "type" and value "value"
// Definiere eine Variable mit dem Namen “variableName” vom Typ "type" und dem Wert "value"
var variableName type = value
Define multiple variables with initial values.
Definiere mehrere Variablen mit einem Startwert.
/*
Define three variables with type "type", and initialize their values.
vname1 is v1, vname2 is v2, vname3 is v3
Definiere drei Variablen vom Typ "type" und gib ihnen drei Startwerte.
vname1 ist gleich v1, vname2 ist gleich v2, vname3 ist gleich v3
*/
var vname1, vname2, vname3 type = v1, v2, v3
Do you think that it's too tedious to define variables use the way above? Don't worry, because the Go team has also found this to be a problem. Therefore if you want to define variables with initial values, we can just omit the variable type, so the code will look like this instead:
Findest Du es nicht auch ein wenig mühsehlig, Variablen auf diesem Weg zu definieren? Keine Sorge, denn das Team hinter Go hat auch so gedacht. Daher kannst Du Variablen Startwerte geben, ohne explizit den Datentyp zu definieren. Der Code würde dann so aussehen:
/*
Define three variables without type "type", and initialize their values.
vname1 is v1vname2 is v2vname3 is v3
Definiere drei Variablen vom Typ "type" und gib ihnen drei Ausgangswerte.
vname1 ist gleich v1, vname2 ist gleich v2, vname3 ist gleich v3
*/
var vname1, vname2, vname3 = v1, v2, v3
Well, I know this is still not simple enough for you. Let's see how we fix it.
Schön, ich weiß das dies immer noch nicht einfach genug für Dich ist. Mal schauen, wie wir das lösen können.
/*
Define three variables without type "type" and without keyword "var", and initialize their values.
vname1 is v1vname2 is v2vname3 is v3
Definiere drei Variablen ohne den Typ "type", ohne das Schlüsselwort "var" und gib ihnen Startwerte.
vname1 ist gleich v1vname2 ist gleich v2vname3 ist gleich v3
*/
vname1, vname2, vname3 := v1, v2, v3
Now it looks much better. Use `:=` to replace `var` and `type`, this is called a brief statement. But wait, it has one limitation: this form can only be used inside of functions. You will get compile errors if you try to use it outside of function bodies. Therefore, we usually use `var` to define global variables and we can use this brief statement in `var()`.
`_` (blank) is a special variable name. Any value that is given to it will be ignored. For example, we give `35` to `b`, and discard `34`.( ***This example just show you how it works. It looks useless here because we often use this symbol when we get function return values.*** )
So sieht es schon viel besser aus. Nutze `:=`, um `var` und `type` zu ersetzen. Es handelt sich hierbei um eine Kurzschreibweise. Aber warte, es gibt einen kleinen Haken: diese Form der Definition kann nur innerhalb von Fuktionen genutzt werden. Der Compiler wird sonst eine Fehlermeldung ausgeben, wenn Du es trotzdem außerhalb der `{}` einer Funktion versuchst. Daher nutzen wir meist das Schlüsselwort `var` um globale Variablen zu definieren oder die Funktion `var()`.
`_` (Unterstrich) ist ein besonderer Variablenname. Jeder übergebene Wert wird ignoriert. Übergeben wir zum Beispiel `35` an `b`, so verwerfen wir `34`. ( ***Dieses Beispiel soll nur die Funktionsweise aufzeigen. Es mag, wie in diesem Fall, nutzlos erscheinen, aber wir werden es oft gebrauchen, wenn wir Rückgabewerte von Funktionen erhalten.*** )
_, b := 34, 35
If you don't use variables that you've defined in your program, the compiler will give you compilation errors. Try to compile the following code and see what happens.
Wenn Du Variablen in Deinem Programm definierst, aber keine Verwendung finden, wird der Compiler eine Fehlermeldung ausgeben. Versuche den unten stehenden Code zu kompilieren und schaue, was passiert.
package main
@@ -59,43 +60,43 @@ If you don't use variables that you've defined in your program, the compiler wil
var i int
}
## Constants
## Konstanten
So-called constants are the values that are determined during compile time and you cannot change them during runtime. In Go, you can use number, boolean or string as types of constants.
Konstanten sind Werte, die während der Kompilierung festgelegt werden und während der Laufzeit nicht veränderbar sind. In Go kannst Du Konstanten als Wert Nummern, Booleans oder Strings geben.
Define constants as follows.
Definiere eine Konstante wie folgt.
const constantName = value
// you can assign type of constants if it's necessary
// Du kannst auch den Datentyp hinzufügen, wenn nötig
const Pi float32 = 3.1415926
More examples.
Mehr Beispiele.
const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"
## Elementary types
## Grundlegende Datentypen
### Boolean
In Go, we use `bool` to define a variable as boolean type, the value can only be `true` or `false`, and `false` will be the default value. ( ***You cannot convert variables' type between number and boolean!*** )
In Go nutzen wir `bool`, um Booleans (Wahrheitswerte) auszudrücken, die entweder den Zustand `true` oder `false` annehmen können, wobei `false` der Standardwert ist. ( ***Du kannst übrigens Nummern zu Booleans und umgekehrt konvertieren!*** )
// sample code
var isActive bool // global variable
var enabled, disabled = true, false // omit type of variables
// Beispielcode
var isActive bool // Globale Variable
var enabled, disabled = true, false // Der Datentyp wird ausgelassen
func test() {
var available bool // local variable
valid := false // brief statement of variable
available = true // assign value to variable
var available bool // Lokale Variable
valid := false // Kurzschreibweise einer Definition
available = true // Eine Variable deklarieren
}
### Numerical types
### Numerische Datentypen
Integer types include both signed and unsigned integer types. Go has `int` and `uint` at the same time, they have same length, but specific length depends on your operating system. They use 32-bit in 32-bit operating systems, and 64-bit in 64-bit operating systems. Go also has types that have specific length including `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Note that `rune` is alias of `int32` and `byte` is alias of `uint8`.
Der Datentyp Integer (ganze Zahlen) kann sowohl den positiven, als auch den negativen Zahlenraum umfassen, was durch ein Vorzeichen kenntlich gemacht wird. Go besitzt `int` und `uint`, welche den selben Wertebereich haben. Dessen Größe hängt aber vom Betriebssystem ab. Es werden 32-Bit unter 32-Bit Betriebssystemen verwendet und 64-Bit unter 64-Bit Betriebssystemen. Go umfasst außerdem Datentypen mit einer spezifischen Länge: `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32` und `uint64`. Bedenke, dass `rune` ein Alias für `int32` ist und `byte` dem `uint8` gleicht.
One important thing you should know that you cannot assign values between these types, this operation will cause compile errors.
Eine wichtige Sache, die Du wissen solltest, ist, dass Du verschiedene Datentypen nicht vermischen kannst, da es sonst zu Fehlern bei der Kompilierung kommt.
var a int8
@@ -103,86 +104,90 @@ One important thing you should know that you cannot assign values between these
c := a + b
Although int32 has a longer length than int8, and has the same type as int, you cannot assign values between them. ( ***c will be asserted as type `int` here*** )
Float types have the `float32` and `float64` types and no type called `float`. The latter one is the default type if using brief statement.
Obwohl int32 einen größeren Wertebereich als int8 abdeckt, und beide vom Typ `int` sind, kannst Du sie nicht miteinander kombinieren. ( ***c wird hier der Typ `int` zugewiesen*** )
That's all? No! Go supports complex numbers as well. `complex128` (with a 64-bit real and 64-bit imaginary part) is the default type, if you need a smaller type, there is one called `complex64` (with a 32-bit real and 32-bit imaginary part). Its form is `RE+IMi`, where `RE` is real part and `IM` is imaginary part, the last `i` is imaginary number. There is a example of complex number.
Floats (Gleitkommazahlen) haben entweder den Datentyp `float32` oder `float64`, aber es gibt keinen Datentyp namens `float`. `float64` wird standardmäßig verwendet, sollte die Kurzschreibweise für eine Variablendekleration genutzt werden.
War das schon alles? Nein! Go unterstützt auch komplexe Zahlen. `complex128` (bestehend aus 64-Bit für den realen Anteil und weiteren 64-Bit für den imaginären Teil) ist der Standarddatentyp. Solltest Du einen kleineren Wertebereich benötigen, kannst `complex64` als Datentyp verwenden (mit 32-Bit für den realen, und nochmals 32-Bit für den imaginären Teil). Die Schreibweise lautet `RE+IMi`, wo `RE` für den realen Teil steht, und `IM` den Imaginären Part repräsentiert. Das `i` am Ende ist die imaginäre Zahl. Hier ist ein Beispiel für eine komplexe Zahl.
var c complex64 = 5+5i
//output: (5+5i)
fmt.Printf("Value is: %v", c)
// Ausgabe: (5+5i)
fmt.Printf("Der Wert ist: %v", c)
### String
### Strings
We just talked about how Go uses the UTF-8 character set. Strings are represented by double quotes `""` or backticks ``` `` ```.
Wir sprachen vorhin darüber, das Go eine native UTF-8 Unterstützung mit sich bringt. Strings (Zeichenketten) werden durch Anführungszeichen `""` gekennzeichet, oder durch Backticks (rückwärts geneigtes Hochkomma) ``` `` ```.
// sample code
var frenchHello string // basic form to define string
var emptyString string = "" // define a string with empty string
// Beispielcode
var frenchHello string // Grundlegende Schreibweise zur Definition eines Strings
var emptyString string = "" // Definiert einen leeren String
func test() {
no, yes, maybe := "no", "yes", "maybe" // brief statement
no, yes, maybe := "no", "yes", "maybe" // Kurzschreibweise
japaneseHello := "Ohaiou"
frenchHello = "Bonjour" // basic form of assign values
frenchHello = "Bonjour" // Grundlegende Deklaration
}
It's impossible to change string values by index. You will get errors when you compile following code.
Es ist unmöglich, die Zeichen eines Strings anhand ihres Index zu verändern. Du wirst eine Fehlermeldung erhalten, solltest Du dies ausprobieren.
var s string = "hello"
var s string = "Hallo"
s[0] = 'c'
What if I really want to change just one character in a string? Try following code.
Aber was ist, wenn ich wirklich nur ein Zeichen in einem String ändern möchte? Probieren wir es mit mit diesem Codebeispiel.
s := "hello"
c := []byte(s) // convert string to []byte type
c := []byte(s) // Konvertiere den String zum Typ []byte
c[0] = 'c'
s2 := string(c) // convert back to string type
s2 := string(c) // Wandle den Wert in eine String zurück
fmt.Printf("%s\n", s2)
You use the `+` operator to combine two strings.
s := "hello,"
m := " world"
Nutze den `+` Operator, um zwei Strings zu verknüpfen.
s := "Hallo"
m := " Welt"
a := s + m
fmt.Printf("%s\n", a)
and also.
s := "hello"
s = "c" + s[1:] // you cannot change string values by index, but you can get values instead.
oder auch
s := "Hallo"
s = "c" + s[1:] // Du kannst die Werte mit Hilfe des Index nicht verändern, aber sie abfragen
fmt.Printf("%s\n", s)
What if I want to have a multiple-line string?
m := `hello
world`
`` ` will not escape any characters in a string.
Was ist, wenn ein String über mehrere Zeilen verlaufen soll?
### Error types
m := `Hallo
Welt`
`\`` wird die Zeichen in einem String escapen (d.h. mit `\` dessen Ausführung verhindern).
Go has one `error` type for purpose of dealing with error messages. There is also a package called `errors` to handle errors.
### Fehlermeldungen
Go besitzt mit `error` einen eigenen Datentyp, um mit Fehlermeldungen umzugehen. Zudem gibt es auch ein Paket mit dem Namen `errors`, um weitere Möglichkeiten bereitzustellen, Fehlermeldungen zu verarbeiten.
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
### Underlying data structure
### Grundeliegende Datenstrukturen
The following picture comes from an article about [Go data structure](http://research.swtch.com/godata) in [Russ Cox Blog](http://research.swtch.com/). As you can see, Go utilizes blocks of memory to store data.
Die folgende Grafik enstammt dem Artikel [Datenstrukturen in Go](http://research.swtch.com/godata) (auf englisch) aus [Russ Coxs Blog](http://research.swtch.com/). Wie Du sehen kannst, nutzt Go Abschnitte des Arbeitsspeichers, um Daten zu speichern.
![](images/2.2.basic.png?raw=true)
Figure 2.1 Go underlying data structure
Abbildung 2.1 Gos grundlegende Datenstrukturen
## Some skills
## Einige Fähigkeiten
### Define by group
### Gruppierte Definition
If you want to define multiple constants, variables or import packages, you can use the group form.
Wenn Du mehrere Konstanten und Variablen deklarieren oder Pakete importieren möchtest, kannst Du dies auch gruppiert durchführen.
Basic form.
Übliche Vorgehensweise.
import "fmt"
import "os"
@@ -195,7 +200,7 @@ Basic form.
var pi float32
var prefix string
Group form.
Gruppierter Ansatz.
import(
"fmt"
@@ -213,242 +218,241 @@ Group form.
pi float32
prefix string
)
Unless you assign the value of constant is `iota`, the first value of constant in the group `const()` will be `0`. If following constants don't assign values explicitly, their values will be the same as the last one. If the value of last constant is `iota`, the values of following constants which are not assigned are `iota` also.
### iota enumerate
Wird innerhalb von `constant()` einer Konstanten das Schlüsselwort `iota` als Wert zugewiesen, hat sie den Wert `0`. Werden den folgenden Konstanten keinee expliziten Werte zugewiesen, wird der letzte zugeweise Wert von `iota` um 1 erhöht und und der folgenden Konstante zugewiesen. Dieses Verhalten beleuchten wir im folgenden Absatz.
Go has one keyword called `iota`, this keyword is to make `enum`, it begins with `0`, increased by `1`.
### Aufzählen mit iota
Go besitzt das Schlüselwort `iota`, um eine Aufzählung zu starten. Der Startwert ist `0` und wird jeweils um `1` erhöht.
const(
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // If there is no expression after the constants name, it uses the last expression, so it's saying w = iota implicitly. Therefore w == 3, and y and x both can omit "= iota" as well.
w // Folgt dem Namen der Konstante keine Deklaration, so wird die zuletzt erfolgte verwendet. w = iota wird somit implizit auf iota gesetzt. Daher gilt w == 3. Folglich kannst Du bei x und y "= iota" einfach auslassen.
)
const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0.
const v = iota // Sobald iota erneut auf `const` trifft, wird erneut mit `0` gestartet, also gilt v = 0.
const (
e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line.
e, f, g = iota, iota, iota // e, f und g haben den selben Wert 0, da sie in der selben Zeile stehen.
)
### Some rules
### Einige Regeln
The reason that Go is concise because it has some default behaviors.
Der Grund, warum Go so prägnant ist, liegt im vorhersehbaren Verhalten der Programmiersprache.
- Any variable that begins with a capital letter means it will be exported, private otherwise.
- The same rule applies for functions and constants, no `public` or `private` keyword exists in Go.
- Jede Variable, die mit einem großgeschriebenen Buchstaben anfängt, kann exportiert werden. Andernfalls ist sie privat.
- Die selbe Regel gilt auch für Funktionen und Konstanten. Schlüsselwörter wie `public` oder `private` existieren in Go nicht.
## array, slice, map
## Array, Slice, Map
### array
### Array
`array` is an array obviously, we define a one as follows.
Ein `array` ist eine Aneinanderreihung von Daten, die wie folgt definiert wird:
var arr [n]type
in `[n]type`, `n` is the length of the array, `type` is the type of its elements. Like other languages, we use `[]` to get or set element values within arrays.
var arr [n]Datentyp
var arr [10]int // an array of type [10]int
arr[0] = 42 // array is 0-based
arr[1] = 13 // assign value to element
fmt.Printf("The first element is %d\n", arr[0]) // get element value, it returns 42
fmt.Printf("The last element is %d\n", arr[9]) //it returns default value of 10th element in this array, which is 0 in this case.
Because length is a part of the array type, `[3]int` and `[4]int` are different types, so we cannot change the length of arrays. When you use arrays as arguments, functions get their copies instead of references! If you want to use references, you may want to use `slice`. We'll talk about later.
wobei in `[n]Datentyp` das `n` die Länge des Arrays angibt. `Datentyp` ist selbsterklärend der Datentyp der Elemente (bzw. der aneinandergereihten Daten). Wie in anderen Programmiersprachen nutzen wir `[]`, um Daten im Array zu speichern oder um sie auszulesen.
It's possible to use `:=` when you define arrays.
var arr [10]int // Ein Array vom Typ [10]int
arr[0] = 42 // Der erste Index des Arrays ist 0
arr[1] = 13 // Einem Element wird ein Wert zugewiesen
fmt.Printf("Das erste Element ist %d\n", arr[0]) // Beim Auslesen des Wertes wird 42 zurückgegeben
fmt.Printf("Das letzte Element ist %d\n", arr[9]) // Es gibt den Standardwert des 10. Elements zurück, was in diesem Fall 0 ist.
a := [3]int{1, 2, 3} // define an int array with 3 elements
Da die Länge ein Teil des Array-Typs ist, sind `[3]int` und `[4]int` verschieden, sodass wir die Länge eines Arrays nicht ändern können. Nutzt Du Arrays als Argument in einer Funktion, dann wird eine Kopie des Arrays übergeben, statt einem Zeiger (bzw. ein Verweis) auf das Original. Möchtest Du stattdessen den Zeiger übergeben, dann musst Du einen `slice` verwenden. Darauf gehen wir aber später nochmals ein.
b := [10]int{1, 2, 3} // define a int array with 10 elements, of which the first three are assigned. The rest of them use the default value 0.
Es ist möglich, `:=` zu nutzen, um ein Array zu deklarieren.
c := [...]int{4, 5, 6} // use `…` to replace the length parameter and Go will calculate it for you.
a := [3]int{1, 2, 3} // Deklariere ein Array vom Typ int mit drei Elementen
b := [10]int{1, 2, 3} // Deklariere ein int-Array mit zehn Elementen, bei dem die ersten Drei einen Wert zugewiesen bekommen. Der Rest erhält den Standardwert 0.
c := [...]int{4, 5, 6} // Nutze `…` statt der Längenangabe. Go wird die Länge dann selbst bestimmen.
You may want to use arrays as arrays' elements. Let's see how to do this.
Vielleicht möchtest Du auch Arrays als Element in einem Array nutzen. Schauen wir mal, wie das geht.
// define a two-dimensional array with 2 elements, and each element has 4 elements.
// Deklariere ein zweidimensionales Array mit zwei Elementen, welche jeweils vier Elemente besitzen.
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// The declaration can be written more concisely as follows.
// Die Dekleration kann auch ein wenig kompakter geschrieben werden.
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
Array underlying data structure.
Arrays sind grundlegende Datenstrukturen in Go.
![](images/2.2.array.png?raw=true)
Figure 2.2 Multidimensional array mapping relationship
Abbildung 2.2 Die Zuordnung in einem mehrdimensionalen Array
### slice
### Slice
In many situations, the array type is not a good choice -for instance when we don't know how long the array will be when we define it. Thus, we need a "dynamic array". This is called `slice` in Go.
In vielen Situationen ist ein Array als Datentyp keine gute Wahl, wenn wir während der Deklaration dessen Länge noch nicht wissen. Daher brauchen wir so etwas wie ein "dynamisches Array" mit einer variabler Länge. Diese werden in Go `slice` genannt.
`slice` is not really a `dynamic array`. It's a reference type. `slice` points to an underlying `array` whose declaration is similar to `array`, but doesn't need length.
`slice` ist nicht wirklich ein `dynamisches Array`. Es ist vielmehr ein Zeiger auf ein darunterliegendes `array` mit einer ähnlichen Deklaration, dass aber keine Länge benötigt.
// just like defining an array, but this time, we exclude the length.
// Man deklariert ein Slice wie ein Array, lässt jedoch die Länge weg.
var fslice []int
Then we define a `slice`, and initialize its data.
Nun deklarieren wir ein `slice` und vergeben Startwerte.
slice := []byte {'a', 'b', 'c', 'd'}
`slice` can redefine existing slices or arrays. `slice` uses `array[i:j]` to slice, where `i` is the start index and `j` is end index, but notice that `array[j]` will not be sliced since the length of the slice is `j-i`.
// define an array with 10 elements whose types are bytes
`slice` kann existierende Slices oder Arrays verändern. `slice` nutzt `array[i:j]` zum "Herrausschneiden" von Elementen. `i` gibt den Index des Startpunkts an und kopiert alle Elemente bis zum Index `j`. Beachte, dass `array[j]` nicht in dem Ausschnitt enthalten ist, da das Slice eine Länge von `j-i` hat.
// Deklariere ein Array mit der Länge 10 von vom Typ byte
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// define two slices with type []byte
// Erstelle zwei Slices vom Typ []byte
var a, b []byte
// 'a' points to elements from 3rd to 5th in array ar.
// 'a' verweist auf die Elemente zwischen Index 3 und 5 im Array ar.
a = ar[2:5]
// now 'a' has elements ar[2],ar[3] and ar[4]
// 'b' is another slice of array ar
b = ar[3:5]
// now 'b' has elements ar[3] and ar[4]
// 'a' besitzt nun die Elemente ar[2],ar[3] und ar[4]
Notice the differences between `slice` and `array` when you define them. We use `[…]` to let Go calculate length but use `[]` to define slice only.
// 'b' ist ein weiterer Ausschnitt (Slice) vom Array ar
b = ar[3:5]
// 'b' besitzt nun die Elemente ar[3] und ar[4]
Their underlying data structure.
Beachte die Unterscheide bei der Deklaration von `slice` und `array`. Wir nutzen `[…]`, um Go die Länge automatisch herausfinden zu lassen, aber nutzen `[]` lediglich zur Deklaration von Slices.
Ihre zugrundeliegenden Datentypen.
![](images/2.2.slice.png?raw=true)
Figure 2.3 Correspondence between slice and array
Abbildung 2.3 Der Zusammenhang zwischen Slices und Arrays
slice has some convenient operations.
Slices haben bestimmte Anwendungsgebiete.
- `slice` is 0-based, `ar[:n]` equals to `ar[0:n]`
- The second index will be the length of `slice` if omitted, `ar[n:]` equals to `ar[n:len(ar)]`.
- You can use `ar[:]` to slice whole array, reasons are explained in first two statements.
- Ein `slice` beginnt mit dem Index 0, `ar[:n]` gleicht `ar[0:n]`.
- Der zweite Index gibt die Länge vom Slice an, wenn er ausgelassen wird. `ar[n:]` gleicht `ar[n:len(ar)]`.
- Du kannst auch `ar[:]` nutzen, um einen gesamtes Array zu kopieren, wie in den ersten beiden Punkten erklärt.
More examples pertaining to `slice`
Mehr Beispiele zu `slice`
// define an array
// Deklariere ein Array
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// define two slices
// Deklariere zwei Slices
var aSlice, bSlice []byte
// some convenient operations
aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c
aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j
aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements
// Einige gewöhnliche Anwendungsfälle
aSlice = array[:3] // ist das Gleiche wie aSlice = array[0:3]. aSlice hat die Elemente a,b,c
aSlice = array[5:] // ist das Gleiche wie aSlice = array[5:10]. aSlice hat die Elemente f,g,h,i,j
aSlice = array[:] // ist das Gleiche wie aSlice = array[0:10]. aSlice beinhaltet alle Elemente
// slice from slice
aSlice = array[3:7] // aSlice has elements d,e,f,glen=4cap=7
bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f
bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f
bSlice = aSlice[0:5] // slice could be expanded in range of cap, now bSlice contains d,e,f,g,h
bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g
// Ein Slice vom Slice
aSlice = array[3:7] // aSlice hat die Elemente d,e,f,gAnfang=4Kapazität=7
bSlice = aSlice[1:3] // bSlice beinhaltet aSlice[1], aSlice[2], also hat es die Elemente e,f
bSlice = aSlice[:3] // bSlice beinhaltet aSlice[0], aSlice[1], aSlice[2], also hat es die Elemente d,e,f
bSlice = aSlice[0:5] // Der Slice ist nun länger, sodass bSlice d,e,f,g,h beinhaltet
bSlice = aSlice[:] // bSlice hat nun die gleiche Elemente wie aSlice, also d,e,f,g
`slice` is a reference type, so any changes will affect other variables pointing to the same slice or array. For instance, in the case of `aSlice` and `bSlice` above, if you change the value of an element in `aSlice`, `bSlice` will be changed as well.
`slice` ist ein Datentyp mit einem Zeiger, sodass Änderungen am Slice auch andere Variablen verändern, die wiederum selbt auf den Slice verweisen. Wie im oberigen Fall von `aSlice` und `bSlice`: veränderst Du den Wert eines Elements in `aSlice`, wird sich dieser auch im `bSlice` ändern.
`slice` is like a struct by definition and it contains 3 parts.
`slice` ist ähnlich wie ein Struct und besteht aus drei Komponenten:
- A pointer that points to where `slice` starts.
- The length of `slice`.
- Capacity, the length from start index to end index of `slice`.
- Ein Zeiger, der auf den Beginn des `slice` bzw. zugrundeliegenden Array verweist.
- Die Länge definiert den Ausschnitt des zugrundeliegenden Arrays.
- Kapazität definiert die max. Größe des zugrundeliegenden Arrays.
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
Slice_a := Array_a[2:5]
The underlying data structure of the code above as follows.
Die zugrundeliegenden Datenstrukturen vom vorherigen Code sehen wie folgt aus:
![](images/2.2.slice2.png?raw=true)
Figure 2.4 Array information of slice
Abbildung 2.4 Arrayelemente eines Slice
There are some built-in functions for slice.
Es existieren eingebaute Funktionen, um mit Slices zu arbeiten.
- `len` gets the length of `slice`.
- `cap` gets the maximum length of `slice`
- `append` appends one or more elements to `slice`, and returns `slice` .
- `copy` copies elements from one slice to the other, and returns the number of elements that were copied.
- `len` gibt die Länge eines `slice` bzw. Ausschnitts zurück.
- `cap` gibt die max. Länge (Kapazität) eines `slice` zurück.
- `append` erweitert den `slice` um ein oder mehrere Elemente, und gibt einen `slice` zurück.
- `copy` erstellt eine Kopie eines Slices, und gibt die Anzahl der kopierten Elemente zurück.
Attention: `append` will change the array that `slice` points to, and affect other slices that point to the same array. Also, if there is not enough length for the slice (`(cap-len) == 0`), `append` returns a new array for this slice. When this happens, other slices pointing to the old array will not be affected.
Achtung: `append` wird das Array, aus das `slice` verweist verändern, sowie andere Slices, die auf das selbe Array verweisen. Sollte de Kapazität des zugrundeliegenden Arrays nicht ausreichen (`(cap-len) == 0`), dann gibt `append` ein neues Array zurück. Dies hat aber keine Auswirkungen auf andere Slices, die auf das alte Array verwiesen.
### map
### Map
`map` behaves like a dictionary in Python. Use the form `map[keyType]valueType` to define it.
`map` verhält sich wie ein Dictionary in Python. Nutze das Schema `map[SchlüsselTyp]WerteTyp`, um es zu deklarieren.
Let's see some code. The 'set' and 'get' values in `map` are similar to `slice`, however the index in `slice` can only be of type 'int' while `map` can use much more than that: for example `int`, `string`, or whatever you want. Also, they are all able to use `==` and `!=` to compare values.
Schauen wir uns ein wenig Code an. Die 'set'- und 'get'-Werte in `map` sind der von `slice` sehr ähnlich. In einem Slice kann der Datentyp des Index nur ein `int` sein. In einer `map` kann es sich jedoch um einen `int`, `string` oder um jeden anderen Datentyp handeln. Du kannst auch `==` und `!=` verwenden, um Werte mit einander zu vergleichen.
// use string as the key type, int as the value type, and `make` initialize it.
// Nutze 'string' als Datentyp für den Schlüssel, 'int' als Datentyp für den Wert und `make` zum Erstellen.
var numbers map[string] int
// another way to define map
numbers := make(map[string]int)
numbers["one"] = 1 // assign value by key
numbers["ten"] = 10
numbers["three"] = 3
// Ein alternativer Weg zum Deklarieren
nummern := make(map[string]int)
nummern["eins"] = 1 // Weise ein Wert durch einen Schlüssel zu
nummern["zehn"] = 10
nummern["drei"] = 3
fmt.Println("The third number is: ", numbers["three"]) // get values
// It prints: The third number is: 3
fmt.Println("Die dritte Nummer lautet: ", nummern["drei"]) // Lese den Wert aus
// Ausgabe: Die dritte Nummer lautet: 3
Some notes when you use map.
Einige Anmerkungen zur Nutzung von maps.
- `map` is disorderly. Everytime you print `map` you will get different results. It's impossible to get values by `index` -you have to use `key`.
- `map` doesn't have a fixed length. It's a reference type just like `slice`.
- `len` works for `map` also. It returns how many `key`s that map has.
- It's quite easy to change the value through `map`. Simply use `numbers["one"]=11` to change the value of `key` one to `11`.
- `map` ist ungeordnet. Jedesmal, wenn Du eine `map` ausgeben willst, erhälst Du ein anderes Ergebnis. Dadurch ist es unmöglich, Werte über den `index` abzufragen. Nutze dafür den entsprechenden `Schlüssel`.
- `map` hat keine feste Länge. Dieser Datentyp ist wie `slice` lediglich ein Verweis.
- `len` funktioniert bei `map` auch. Es werden die Anzahl der `Schlüssel` zurückgegeben.
- Es ist ziemlich einfach, der Wert in einer `map` zu ändern. Nutze `nummern["eins"]=11`, um den `Schlüssel` one den Wert `11` zuzuweisen.
You can use form `key:val` to initialize map's values, and `map` has built-in methods to check if the `key` exists.
Du kannst das Schema `Schlüssel:Wert` nutzen, um eine `map` mit Startwerten zu erstellen. `map` hat auch eingebaute Funktionen, um die Existenz eines `key` zu überprüfen.
Use `delete` to delete an element in `map`.
Benutze `delete`, um ein Element in `map` zu löschen.
// Initialize a map
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map has two return values. For the second return value, if the key doesn't exist'ok' returns false. It returns true otherwise.
csharpRating, ok := rating["C#"]
// Erstelle eine map
bewertung := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// Map hat zwei Rückgabewerte. Der zweite Rückgabewert 'ok' wird, wenn der Schlüssel nicht existiertaus false gesetzt. Andernfalls wird true zurückgegeben.
csharpBewertung, ok := bewertung["C#"]
if ok {
fmt.Println("C# is in the map and its rating is ", csharpRating)
fmt.Println("C# ist in der map hat die Bewertung ", csharpBewertung)
} else {
fmt.Println("We have no rating associated with C# in the map")
fmt.Println("Es konnte keine Bewertung für C# in der map gefunden werden.")
}
delete(rating, "C") // delete element with key "c"
delete(bewertung, "C") // Lösche das Element mit dem Schlüssel "c"
As I said above, `map` is a reference type. If two `map`s point to same underlying data, any change will affect both of them.
Wie ich bereits sagte, ist `map` lediglich ein Verweis. Verweisen zwei `map`s auf die gleiche, zugrundeliegende Datenstruktur, wird eine Änderung Auswirkungen auf beide `map`s haben.
m := make(map[string]string)
m["Hello"] = "Bonjour"
m["Hallo"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // now the value of m["hello"] is Salut
m1["Hallo"] = "Salut" // Nun ist der Wert von m["hello"] Salut
### make, new
`make` does memory allocation for built-in models, such as `map`, `slice`, and `channel`, while `new` is for types' memory allocation.
`make` reserviert Speicher für die eingebauten Datentypen, wie `map`, `slice`, und `channel`, wo hingegen `new` für selbstdefinierte Datentype (durch `type` definiert) Speicher zuweist.
`new(T)` allocates zero-value to type `T`'s memory, returns its memory address, which is the value of type `*T`. By Go's definition, it returns a pointer which points to type `T`'s zero-value.
`new(T)` ordnet dem Datentyp `T` Speicherplatz zu und initialisiert diesen mit den Standardwerten des jeweiligen Datentyps (z.B. `false` für einen `bool`). Anschließend wird die Adresse des Speicherortes in des Datentyps `*T` zurückgegeben. Nach der Definition von Go ist dies ein Zeiger, welcher auf die Standardwerte der Initilisierung von `T` verweist.
`new` returns pointers.
`new` gibt Zeiger als Wert zurück.
The built-in function `make(T, args)` has different purposes than `new(T)`. `make` can be used for `slice`, `map`, and `channel`, and returns a type `T` with an initial value. The reason for doing this is because the underlying data of these three types must be initialized before they point to them. For example, a `slice` contains a pointer that points to the underlying `array`, length and capacity. Before these data are initialized, `slice` is `nil`, so for `slice`, `map` and `channel`, `make` initializes their underlying data and assigns some suitable values.
Die eingebaute Funktion `make(T, args)` hat einen anderen Zweck als `new(T)`. `make` kann für `slice`, `map`, und `channel` genutzt werden und gibt den Datentyp `T` mit seinem definierten Startwert zurück. Der Grund liegt darin, dass das ein Objekt vom zugrundeliegenden Datentyp erst erstellt wird, bevor auf diesen verwiesen werden kann. Dies ist bei diesen drei Datentypen der Fall. Zum Beispiel beinhaltet `slice` einen Zeiger, der auf das zugrundeliegende Array, die Länge und die Kapazität verweist. Vor der Initialisierung der Daten beinhaltet `slice` jedoch nur `nil`. Daher vergibt `make` den Datentypen `slice`, `map` und `channel` geeignetere Werte.
`make` returns non-zero values.
`make` gibt dabei die angesprochenen Standardwerte der entsprechenden Datentypen zurück (z.B. `false` für einen `bool`).
The following picture shows how `new` and `make` are different.
Die folgende Grafik verdeutlicht den Unterschied zwischen `new` und `make`.
![](images/2.2.makenew.png?raw=true)
Figure 2.5 Underlying memory allocation of make and new
Abbildung 2.5 Wie make und new Datenstrukturen Speicherplatz zuweisen
Zero-value does not mean empty value. It's the value that variables default to in most cases. Here is a list of some zero-values.
Standardwerte besitzen einen Wert. Dies sind die gebräuchlichsten Anwendungsfälle. Hier eine kleine Liste von Standardwerten.
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 // the actual type of rune is int32
byte 0x0 // the actual type of byte is uint8
float32 0 // length is 4 byte
float64 0 //length is 8 byte
rune 0 // rune ist ein Alias für int32
byte 0x0 // byte ist ein Alias für uint8
float32 0 // Die Größe beträgt 4 Byte
float64 0 // Die Größe beträgt 8 Byte
bool false
string ""
## Links
- [Directory](preface.md)
- Previous section: ["Hello, Go"](02.1.md)
- Next section: [Control statements and functions](02.3.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: ["Hallo Go"](02.1.md)
- Nächster Abschnitt: [Kontrollstrukturen und Funktionen](02.3.md)

View File

@@ -1,516 +1,523 @@
# 2.3 Control statements and functions
# 2.3 Kontrollstrukturen und Funktionen
In this section, we are going to talk about control statements and function operations in Go.
In diesem Kapite
werfen wir einen Blick auf Kontrollstrukturen und Funktionen in Go.
## Control statement
## Kontrollstrukturen
The greatest invention in programming is flow control. Because of them, you are able to use simple control statements that can be used to represent complex logic. There are three categories of flow control: conditional, cycle control and unconditional jump.
Die beste Erfindung in der Programmierung ist die Möglichkeit, den Ablauf einen Programms dynamisch verändern zu können. Um dies zu ermöglichen, kannst Du Kontrollstrukturen verwenden, um komplexe, logische Verknüpfungen darzustellen. Es gibt drei Arten, den Programmablauf zu ändern: Bedingungen, Schleifen, die eine Aufgabe mehrmals durchführen können und Sprungmarken.
### if
`if` will most likely be the most common keyword in your programs. If it meets the conditions, then it does something and it does something else if not.
`if` ist das am weitverbreiteste Schlüsselwort in Deinen Programmen. Sollte eine Bedingung erfüllt sein, wird das Programm eine bestimmte Aufgabe ausführen. Wird die Bedingung nicht erfüllt, dann passiert etwas Anderes.
`if` doesn't need parentheses in Go.
In Go braucht man keine runden Klammern für eine `if`-Bedingung nutzen.
if x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than or equal to 10")
}
The most useful thing concerning `if` in Go is that it can have one initialization statement before the conditional statement. The scope of the variables defined in this initialization statement are only available inside the block of the defining `if`.
if x > 10 {
fmt.Println("x ist größer als 10")
} else {
fmt.Println("x ist kleiner gleich 10")
}
// initialize x, then check if x greater than
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
}
Der nützlichste Aspekt in Bezug auf `if` ist, dass man vor der Überprüfung der Bedingung noch Startwerte vergeben kann. Die Variablen mit den Startwerten sind aber nur im `if`-Block selbst abrufbar.
// the following code will not compile
fmt.Println(x)
Use if-else for multiple conditions.
// Gebe x einen Startwert und überprüfe dann, ob x größer als 10 ist
if x := berechneEinenWert(); x > 10 {
fmt.Println("x ist größer als 10")
} else {
fmt.Println("x ist kleiner als 10")
}
// Der folgende Code kann nicht kompiliert werden
fmt.Println(x)
Benutze if-else für mehrere Bedingungen.
if integer == 3 {
fmt.Println("Der Integer ist gleich 3")
} else if integer < 3 {
fmt.Println("Der Integer ist kleiner als 3")
} else {
fmt.Println("Der Integer ist größer als 3")
}
if integer == 3 {
fmt.Println("The integer is equal to 3")
} else if integer < 3 {
fmt.Println("The integer is less than 3")
} else {
fmt.Println("The integer is greater than 3")
}
### goto
Go has a `goto` keyword, but be careful when you use it. `goto` reroutes the control flow to a previously defined `label` within the body of same code block.
func myFunc() {
i := 0
Here: // label ends with ":"
fmt.Println(i)
i++
goto Here // jump to label "Here"
}
The label name is case sensitive.
Go umfasst auch das `goto` Schlüsselwort, aber benutze es weise. `goto` verändert den Ablauf des Programms, indem es an einer vorher definierten Sprungmarke im selben Codeabschnitt weiterläuft.
func meineFunktion() {
i := 0
Hier: // Sprungmarken enden mit einem ":"
fmt.Println(i)
i++
goto Hier // Springe nun zur Sprungmarke "Hier"
}
Es wird zwischen der Groß- und Kleinschreibung von Sprungmarken unterschieden. `Hier` ist nicht das Selbe wie `hier`.
### for
`for` is the most powerful control logic in Go. It can read data in loops and iterative operations, just like `while`.
`for` ist die mächtigste Kontrollstruktur in Go. Sie kann Daten lesen und sie in jedem Durchgang verändern, ähnlich wie `while`.
for expression1; expression2; expression3 {
//...
}
`expression1`, `expression2` and `expression3` are all expressions, where `expression1` and `expression3` are variable definitions or return values from functions, and `expression2` is a conditional statement. `expression1` will be executed once before looping, and `expression3` will be executed after each loop.
for Ausdruck1; Ausdruck2; Ausdruck3 {
//...
}
Examples are more useful than words.
`Ausdruck1`, `Ausdruck2` und `Ausdruck3` sind alle Ausdrücke, aber bei `Ausdruck1` und `Ausdruck3` handelt es sich aber entweder um eine Deklaration von Variablen oder um Rückgabewerte von Funktionen. `Ausdruck2` ist dagegen eine zu erfüllende Bedingung . `Ausdruck1` wird nur einmal vor der Ausführung der Schleife verändert, aber `Ausdruck3` kann mit jedem Durchlauf verändert werden.
package main
import "fmt"
Aber Beispiele sagen mehr als tausend Worte.
func main(){
sum := 0;
for index:=0; index < 10 ; index++ {
sum += index
}
fmt.Println("sum is equal to ", sum)
}
// Printsum is equal to 45
package main
import "fmt"
Sometimes we need multiple assignments, but Go doesn't have the `,` operator, so we use parallel assignment like `i, j = i + 1, j - 1`.
func main(){
summe := 0;
for index:=0; index < 10 ; index++ {
summe += index
}
fmt.Println("summe hat den Wert ", summe)
}
// Ausgabe: summe hat den Wert 45
We can omit `expression1` and `expression3` if they are not necessary.
Manchmal müssen wir am Anfang mehrere Deklarationen vornehmen. Da Go für die Trennung der Variablen über kein `,` verfügt, können die Variablen nicht einzeln deklariert werden. Ein Ausweg bietet die parallele Deklaration: `i, j = i + 1, j - 1`.
sum := 1
for ; sum < 1000; {
sum += sum
}
Omit `;` as well. Feel familiar? Yes, it's identical to `while`.
Wenn nötig, können wir `Ausdruck1` und `Ausdruck3` auch einfach auslassen.
sum := 1
for sum < 1000 {
sum += sum
}
There are two important operations in loops which are `break` and `continue`. `break` jumps out of the loop, and `continue` skips the current loop and starts the next one. If you have nested loops, use `break` along with labels.
summe := 1
for ; summe < 1000; {
summe += summe
}
for index := 10; index>0; index-- {
if index == 5{
break // or continue
}
fmt.Println(index)
}
// break prints 10、9、8、7、6
// continue prints 10、9、8、7、6、4、3、2、1
`for` can read data from `slice` and `map` when it is used together with `range`.
Auch das `;` kann ausgelassen werden. Kommt es Dir bekannt vor? Denn ist das gleiche Verhalten wie bei `while`.
for k,v:=range map {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
}
Because Go supports multi-value returns and gives compile errors when you don't use values that were defined, you may want to use `_` to discard certain return values.
summe := 1
for summe < 1000 {
summe += summe
}
Es gibt mit `break` und `continue` zwei wichtige Schlüsselwörter bei der Verwendung von Schleifen. `break` wird zum "Ausbrechen" aus einer Schleife genutzt, während `continue` zum nächsten Durchlauf springt. Sollten verschachtelte Schleifen genutzt werden, dann kombiniere `break` mit einer Sprungmarke.
for index := 10; index>0; index-- {
if index == 5{
break // oder continue
}
fmt.Println(index)
}
// break gibt 10、9、8、7、6 aus
// continue gibt 10、9、8、7、6、4、3、2、1 aus
`for` kann auch die Elemente aus einem `slice` oder einer `map` lesen, wenn dies im Zusammenspiel mit `range` geschieht.
for k, v := range map {
fmt.Println("map's Schlüssel:",k)
fmt.Println("map's Wert:",v)
}
Go unterstützt die Rückgabe mehrerer Werte und gibt eine Fehlermeldung aus, wenn nicht genauso viele Variablen zum Speichern vorhanden sind. Solltest Du einen Wert nicht brauchen, kannst Du ihn mit `_` verwerfen.
for _, v := range map{
fmt.Println("map's Wert:", v)
}
for _, v := range map{
fmt.Println("map's val:", v)
}
### switch
Sometimes you may find that you are using too many `if-else` statements to implement some logic, which may make it difficult to read and maintain in the future. This is the perfect time to use the `switch` statement to solve this problem.
Manchmal findest Du Dich in einer Situation vor, indem viele Bedingungen mit `if-else` geprüft werden müssen. Der Code kann dann schnell unleserlich und schwer anpassbar werden. Alternativ kannst Du Bedingungen auch mit `switch` prüfen.
switch sExpr {
case expr1:
some instructions
case expr2:
some other instructions
case expr3:
some other instructions
default:
other code
}
The type of `sExpr`, `expr1`, `expr2`, and `expr3` must be the same. `switch` is very flexible. Conditions don't have to be constants and it executes from top to bottom until it matches conditions. If there is no statement after the keyword `switch`, then it matches `true`.
switch Audruck {
case Fall1:
Eine Aufgabe
case Fall3:
Eine andere Aufgabe
case Fall3:
Eine andere Aufgabe
default:
Anderer Code
}
i := 10
switch i {
case 1:
fmt.Println("i is equal to 1")
case 2, 3, 4:
fmt.Println("i is equal to 2, 3 or 4")
case 10:
fmt.Println("i is equal to 10")
default:
fmt.Println("All I know is that i is an integer")
}
In the fifth line, we put many values in one `case`, and we don't need to add the `break` keyword at the end of `case`'s body. It will jump out of the switch body once it matched any case. If you want to continue to matching more cases, you need to use the`fallthrough` statement.
Der Datentyp von `Fall1`, `Fall2` und `Fall3` muss der selbe sein. `switch` kann sehr flexible eingesetzt werden. Bedingungen müssen keine Konstanten sein und die einzelnen Fälle werden von oben nach unten geprüft, bis eine passender gefunden wurde. Sollte `switch` kein Ausdruck folgen, dann dann wird von einem `bool` ausgegangen und es wird der Code im Fall `true` ausgeführt.
integer := 6
switch integer {
case 4:
fmt.Println("integer <= 4")
fallthrough
case 5:
fmt.Println("integer <= 5")
fallthrough
case 6:
fmt.Println("integer <= 6")
fallthrough
case 7:
fmt.Println("integer <= 7")
fallthrough
case 8:
fmt.Println("integer <= 8")
fallthrough
default:
fmt.Println("default case")
}
This program prints the following information.
i := 10
switch i {
case 1:
fmt.Println("i ist gleich 1")
case 2, 3, 4:
fmt.Println("i ist gleich 2, 3 oder 4")
case 10:
fmt.Println("i ist gleich 10")
default:
fmt.Println("ich weiß nur, dass i ein Integer ist")
}
integer <= 6
integer <= 7
integer <= 8
default case
In der fünften Zeile nutzen wir gleich mehrere Fälle auf einmal. Außerdem brauchen wir nicht `break` am Ende jedes Falls einfügen, wie es in anderen Programmiersprachen üblich ist. Mit dem Schlüsselwort `fallthrough` kannst Du nach einem Treffer auch alle folgenden Fälle ausführen.
## Functions
integer := 6
switch integer {
case 4:
fmt.Println("integer <= 4")
fallthrough
case 5:
fmt.Println("integer <= 5")
fallthrough
case 6:
fmt.Println("integer <= 6")
fallthrough
case 7:
fmt.Println("integer <= 7")
fallthrough
case 8:
fmt.Println("integer <= 8")
fallthrough
default:
fmt.Println("Standardfall")
}
Use the `func` keyword to define a function.
Das Programm gibt folgende Informationen aus.
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
// function body
// multi-value return
return value1, value2
}
We can extrapolate the following information from the example above.
integer <= 6
integer <= 7
integer <= 8
Standardfall
- Use keyword `func` to define a function `funcName`.
- Functions have zero, one or more than one arguments. The argument type comes after the argument name and arguments are separated by `,`.
- Functions can return multiple values.
- There are two return values named `output1` and `output2`, you can omit their names and use their type only.
- If there is only one return value and you omitted the name, you don't need brackets for the return values.
- If the function doesn't have return values, you can omit the return parameters altogether.
- If the function has return values, you have to use the `return` statement somewhere in the body of the function.
## Funktionen
Let's see one practical example. (calculate maximum value)
Nutze das Schlüsselwort `func`, um eine Funktion einzuleiten.
package main
import "fmt"
func funcName(eingabe1 typ1, eingabe2 typ2) (ausgabe1 typ1, ausgabe2 typ2) {
// Körper bzw. Rumpf der Funktion
// Mehrere Werte werden zurückgegeben
return wert1, wert2
}
// return greater value between a and b
func max(a, b int) int {
if a > b {
return a
}
return b
}
Dem gezeigten Beispiel können wir somit folgende Informationen entnehmen:
func main() {
x := 3
y := 4
z := 5
- Nutze das Schlüsselwort `func`, um die Funktion `funcName` zu erstellen.
- Funktionen können ein, mehrere oder kein Argument übergeben werden. Der Datentyp wird nach dem Argument angegeben und Argumente werden durch ein `,` getrennt.
- Funktionen können mehrere Werte zurückgeben.
- Es gibt zwei Rückgabewerte mit den Namen `ausgabe1` and `ausgabe2`. Du kannst Ihren Namen auch einfach auslassen und nur den Datentyp im Kopf angeben.
- Wenn es nur einen Rückgabewert gibt, kannst Du die Klammer um den Datentyp im Kopf weglassen.
- Wenn keine Daten zurückgegeben werden, kannst Du `return` einfach weglassen.
- Um Werte im Körper der Funktion zurückzugeben, musst Du `return` verwenden.
max_xy := max(x, y) // call function max(x, y)
max_xz := max(x, z) // call function max(x, z)
Gucken wir uns mal ein Beispiel an, in dem wir den Größeren von zwei Werten bestimmen.
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // call function here
}
package main
import "fmt"
In the above example, there are two arguments in the function `max`, their types are both `int` so the first type can be omitted. For instance, `a, b int` instead of `a int, b int`. The same rules apply for additional arguments. Notice here that `max` only has one return value, so we only need to write the type of its return value -this is the short form of writing it.
// Gib den größeren Wert von a und b zurück
func max(a, b int) int {
if a > b {
return a
}
return b
}
### Multi-value return
func main() {
x := 3
y := 4
z := 5
One thing that Go is better at than C is that it supports multi-value returns.
max_xy := max(x, y) // ruft die Funktion max(x, y) auf
max_xz := max(x, z) // ruft die Funktion max(x, z) auf
We'll use the following example here.
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // rufe die Funktion hier auf
}
package main
import "fmt"
Im oberen Beispiel übergeben wir jeweils zwei Argumente vom Typ `int` an die Funktion `max`. Da beide Argumente denn gleichen Datentyp besitzen, reicht es, wenn wir diesen nur einmal angeben. So kannst Du statt `a int, b int` einfach `a, b int` schreiben. Dies gilt auch für weitere Argumente. Wie Du sehen kannst, wird nur ein Wert von `max` zurückgegeben, sodass wir den Namen der Variable weglassen können. Dies wäre einfach die Kurzschreibweise.
// return results of A + B and A * B
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}
### Mehrere Rückgabewerte bei Funktionen
func main() {
x := 3
y := 4
Die Möglichkeit, mehrere Werte zurückgeben zu können, ist ein Aspekt, in der Go der Programmiersprache C überlegen ist.
xPLUSy, xTIMESy := SumAndProduct(x, y)
Schauen wir uns das folgende Beispiel an.
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}
The above example returns two values without names -you have the option of naming them also. If we named the return values, we would just need to use `return` to return the values since they are initialized in the function automatically. Notice that if your functions are going to be used outside of the package, which means your function names start with a capital letter, you'd better write complete statements for `return`; it makes your code more readable.
package main
import "fmt"
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B
Multiplied = A*B
return
}
// Gebe die Ergebnisse von A + B und A * B zurück
func SummeUndProdukt(A, B int) (int, int) {
return A+B, A*B
}
### Variable arguments
func main() {
x := 3
y := 4
Go supports variable arguments, which means you can give an uncertain numbers of argument to functions.
xPLUSy, xMALy := SummeUndProdukt(x, y)
func myfunc(arg ...int) {}
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xMALy)
}
`arg …int` tells Go that this is a function that has variable arguments. Notice that these arguments are type `int`. In the body of function, the `arg` becomes a `slice` of `int`.
Das obere Beispiele gibt zwei namenlose Werte zurück. Aber Du kannst sie natürlich auch bennenen, wenn Du magst. Würden wir dies tun, müssten wir mit `return` nur dessen Namen zurückgeben, da die Variablen in der Funktion bereits automatisch initialisiert wurden. Bedenke, dass Funktionen mit einen großen Buchstaben anfangen müssen, wenn Du sie außerhalb eines Pakets verwenden möchtest. Es wäre auch zu empfehlen, die Rückgabewerte zu bennenen, da dies den Code verständlicher macht.
for _, n := range arg {
fmt.Printf("And the number is: %d\n", n)
}
### Pass by value and pointers
func SummeUndProdukt(A, B int) (addiert int, multipliziert int) {
addiert = A+B
multipliziert = A*B
return
}
When we pass an argument to the function that was called, that function actually gets the copy of our variables so any change will not affect to the original variable.
### Variablen als Argumente
Let's see one example in order to prove what i'm saying.
Go unterstützt auch Variablen als Argumente, was bedeutet, das einer Funktionen auch eine unbekannte Anzahl an Argumenten übergeben werden kann.
package main
import "fmt"
func myfunc(arg ...int) {}
// simple function to add 1 to a
func add1(a int) int {
a = a+1 // we change value of a
return a // return new value of a
}
`arg …int` besagt, dass diese Funktionen beliebig viele Argumente entgegennimmt. Beachte, dass alle Argumente vom Typ `int` sind. Im Funktionskörper wird `arg` zu einem `slice` vom Typ `int`.
func main() {
x := 3
for _, n := range arg {
fmt.Printf("Und die Nummer lautet: %d\n", n)
}
### Werte und Zeiger übergeben
fmt.Println("x = ", x) // should print "x = 3"
Wenn wir ein Argument einer aufgerufenen Funktion übergeben, dann bekommt diese meist eine Kopie des Arguments und kann somit das Original nicht beeinflussen.
x1 := add1(x) // call add1(x)
Lass mich Dir ein Beispiel zeigen, um es zu beweisen.
fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
fmt.Println("x = ", x) // should print "x = 3"
}
Can you see that? Even though we called `add1` with `x`, the origin value of `x` doesn't change.
package main
import "fmt"
The reason is very simple: when we called `add1`, we gave a copy of `x` to it, not the `x` itself.
// Simple Funktion, die a um 1 erhöht
func add1(a int) int {
a = a+1 // Wir verändern den Wert von a
return a // Wir geben den Wert von a zurück
}
Now you may ask how I can pass the real `x` to the function.
func main() {
x := 3
We need use pointers here. We know variables are stored in memory and they have some memory addresses. So, if we want to change the value of a variable, we must change its memory address. Therefore the function `add1` has to know the memory address of `x` in order to change its value. Here we pass `&x` to the function, and change the argument's type to the pointer type `*int`. Be aware that we pass a copy of the pointer, not copy of value.
fmt.Println("x = ", x) // Sollte "x = 3" ausgeben
package main
import "fmt"
x1 := add1(x) // Rufe add1(x) auf
// simple function to add 1 to a
func add1(a *int) int {
*a = *a+1 // we changed value of a
return *a // return new value of a
}
fmt.Println("x+1 = ", x1) // Sollte "x+1 = 4" ausgeben
fmt.Println("x = ", x) // Sollte "x = 3" ausgeben
}
func main() {
x := 3
Siehst Du es? Selbst wenn wir der Funktion `add1` die Variable `x` als Argument übergeben, um sie um 1 zu erhöhen, so blieb `x` selbst unverändert
fmt.Println("x = ", x) // should print "x = 3"
Der Grund dafür ist naheliegend: wenn wir `add1` aufrufen, übergeben wir ihr eine Kopie von `x`, und nicht `x` selbst.
x1 := add1(&x) // call add1(&x) pass memory address of x
Nun fragst Du dich bestimmt, wie ich das echte `x` einer Funktion übergeben kann.
fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
fmt.Println("x = ", x) // should print "x = 4"
}
Now we can change the value of `x` in the functions. Why do we use pointers? What are the advantages?
Dafür müssen wir Zeiger verwenden. Wir wissen, das Variablen im Arbeitspeicher hinterlegt sind und sie eine Adresse für den Speicherort besitzen. Um einen Wert zu ändern, müssen wir auch die Adresse des Speicherorts wissen. Daher muss der Funktion `add1` diese Adresse von `x` bekannt sein, um diese ändern zu können. Hier übergeben wir `&x` der Funktion als Argument und ändern damit den Datentyp des Arguments in `*int`. Beachte, dass wir eine Kopie des Zeigers übergeben, und nicht des Wertes.
- Allows us to use more functions to operate on one variable.
- Low cost by passing memory addresses (8 bytes), copy is not an efficient way, both in terms of time and space, to pass variables.
- `string`, `slice`, `map` are reference types, so they use pointers when passing to functions by default. (Attention: If you need to change the length of `slice`, you have to pass pointers explicitly)
package main
import "fmt"
// Simple Funktion, um a um 1 zu erhöhen
func add1(a *int) int {
*a = *a+1 // Wir verändern den Wert von a
return *a // Wir geben den Wert von a zurück
}
func main() {
x := 3
fmt.Println("x = ", x) // Sollte "x = 3" ausgeben
x1 := add1(&x) // Rufe add1(&x) auf und übergebe die Adresse des Speicherortes von x
fmt.Println("x+1 = ", x1) // Sollte "x+1 = 4" ausgeben
fmt.Println("x = ", x) // Sollte "x = 4" ausgeben
}
Nun können wir den Wert von `x` in der Funktion ändern. Aber warum nutzen wir Zeiger? Was sind die Vorteile?
- Es erlaubt uns, mit mehreren Funktionen eine Variable direkt zu benutzen und zu verändern.
- Es braucht wenig Ressourcen, um die Speicheradresse (8 Bytes) zu übergeben. Kopien sind weniger effizient im Kontext von Zeit und Speichergröße.
- `string`, `slice` und `map` sind Referenztypen, die automatisch die Referenz (bzw. den Zeiger) der Funktion übergeben. (Achtung: Wenn Du die Länge eines `slice` ändern möchtest, musst Du den Zeiger explizit übergeben).
### defer
Go has a well designed keyword called `defer`. You can have many `defer` statements in one function; they will execute in reverse order when the program executes to the end of functions. In the case where the program opens some resource files, these files would have to be closed before the function can return with errors. Let's see some examples.
Go besitzt mit `defer` ein weiteres nützliches Schlüsselwort. Du kannst `defer` mehrmals in einer Funktion nutzen. Sie werden in umgekehrter Reihenfolge am Ende einer Funktion ausgeführt. Im dem Fall, dass Dein Programm eine Datei öffnet, muss diese erst wieder geschlossen werden, bevor Fehler zurückgeben werden können. Schauen wir uns ein paar Beispiele an.
func ReadWrite() bool {
file.Open("file")
// Do some work
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
func LesenSchreiben() bool {
file.Open("Datei")
// Mache etwas
if FehlerX {
file.Close()
return false
}
file.Close()
return true
}
We saw some code being repeated several times. `defer` solves this problem very well. It doesn't only help you to write clean code but also makes your code more readable.
if FehlerY {
file.Close()
return false
}
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
If there are more than one `defer`s, they will execute by reverse order. The following example will print `4 3 2 1 0`.
file.Close()
return true
}
Wir haben gesehen, dass hier der selbe Code öfters wiederhohlt wird. `defer` löst dieses Problem ziemlich elegant. Es hilft Dir nicht nur sauberen Code zu schreiben, sodern fördert auch noch dessen Lesbarkeit.
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
### Functions as values and types
func LesenSchreiben() bool {
file.Open("Datei")
defer file.Close()
if FehlerX {
return false
}
if FehlerY {
return false
}
return true
}
Wird `defer` mehr als einmal genutzt, werden sie in umgekehrter Reihenfolge ausgeführt. Das folgende Beispiel wird `4 3 2 1 0` ausgeben.
Functions are also variables in Go, we can use `type` to define them. Functions that have the same signature can be seen as the same type.
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
### Funktionen als Werte und Datentypen
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
What's the advantage of this feature? The answer is that it allows us to pass functions as values.
Funktionen sind auch Variablen in Go, die wir mit `type` definieren können. Funktionen mit der selben Signatur, also dem gleichen Aufbau, können als der selbe Datentyp betrachtet werden.
package main
import "fmt"
type schreibeName func(eingabe1 eingabeTyp1 , eingabe2 eingabeTyp2 [, ...]) (ergebnis1 ergebnisTyp1 [, ...])
type testInt func(int) bool // define a function type of variable
Was ist der Vorteil dieser Fähigkeit? Ganz einfach: wir können somit Funktionen als Werte übergeben.
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
package main
import "fmt"
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
type testeInt func(int) bool // Definiere eine Funktion als Datentyp
// pass the function `f` as an argument to another function
func istUngerade(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func istGerade(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // use function as values
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven)
fmt.Println("Even elements of slice are: ", even)
}
It's very useful when we use interfaces. As you can see `testInt` is a variable that has function type, and return values and arguments of `filter` are the same as `testInt`. Therefore, we can have complex logic in our programs, while maintaining flexibility in our code.
// Übergebe die Funktion f als Argument an eine Andere Funktion
### Panic and Recover
func filter(slice []int, f testeInt) []int {
var ergebnis []int
for _, wert := range slice {
if f(wert) {
ergebnis = append(ergebnis, wert)
}
}
return ergebnis
}
Go doesn't have `try-catch` structure like Java does. Instead of throwing exceptions, Go uses `panic` and `recover` to deal with errors. However, you shouldn't use `panic` very much, although it's powerful.
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
ungerade := filter(slice, istUngerade) // Nutze die Funktion als Wert
fmt.Println("Die ungeraden Elemente von slice lauten: ", ungerade)
gerade := filter(slice, istGerade)
fmt.Println("Die geraden Elemente von slice lauten: ", gerade)
}
Panic is a built-in function to break the normal flow of programs and get into panic status. When a function `F` calls `panic`, `F` will not continue executing but its `defer` functions will continue to execute. Then `F` goes back to the break point which caused the panic status. The program will not terminate until all of these functions return with panic to the first level of that `goroutine`. `panic` can be produced by calling `panic` in the program, and some errors also cause `panic` like array access out of bounds errors.
Dies ist sehr nützlich, wenn wir Interfaces nutzen. Wie Du sehen kannst, wird `testeInt` eine Funktion als Argument übergeben, und die Rückgabewerte besitzen genau den gleichen Datentyp. Somit können wir komplexe Zusammenhänge in unseren Programen flexible warten und erweitern.
Recover is a built-in function to recover `goroutine`s from panic status. Calling `recover` in `defer` functions is useful because normal functions will not be executed when the program is in the panic status. It catches `panic` values if the program is in the panic status, and it gets `nil` if the program is not in panic status.
### Panic und Recover
The following example shows how to use `panic`.
Go besitzt keine `try-catch`-Struktur, wie es in Java der Fall ist. Statt eine Exception zu erstellen, nutzt Go `panic` und `recover`. Auch wenn `panic` sehr mächtig ist, solltest Du es nicht all zu oft benutzen.
var user = os.Getenv("USER")
`panic` ist eine eingebaute Funktion, um den normalen Ablauf des Programms zu unterbrechen und es in eine Art Panikmodus zu versetzen. Wenn die Funktion `F` `panic` aufruft, wird in ihr außer `defer` nichts weiter ausgeführt. Danach geht das Programm zum Ursprungsort des Fehlers zurück. Dies wird nicht geschlossen, ehe alle Funktionen einer `goroutine` `panic` zurückgeben. Einige Fehler erzeugen auch selbst einen Aufruf von `panic`, z.B. wenn der reservierte Speicherplatz eines Arrays nicht ausreicht.
func init() {
if user == "" {
panic("no value for $USER")
}
}
The following example shows how to check `panic`.
`recover` ist ebenfalls eine eingebaute Fuktion, um `goroutinen` vom Panikmodus zu erlösen. Das Aufrufen von `recover` im Zusammenspiel mit `defer` ist sehr nützlich, da normale Funktionen nicht mehr ausgeführt werden, wenn sich das Programm erst einmal im Panikmodus befindet. Die Werte von `panic` werden so aufgefangen, wenn sich das Programm im Panikmodus befindet. Ansonsten wird `nil` zurückgegeben.
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() // if f causes panic, it will recover
return
}
### `main` function and `init` function
Das folgende Beispiel soll die Nutzung von `panic` verdeutlichen.
Go has two retentions which are called `main` and `init`, where `init` can be used in all packages and `main` can only be used in the `main` package. These two functions are not able to have arguments or return values. Even though we can write many `init` functions in one package, I strongly recommend writing only one `init` function for each package.
var benutzer = os.Getenv("BENUTZER")
Go programs will call `init()` and `main()` automatically, so you don't need to call them by yourself. For every package, the `init` function is optional, but `package main` has one and only one `main` function.
func init() {
if benutzer == "" {
panic("Kein Wert für $BENUTZER gefunden")
}
}
Das nächste Beispiel zeigt, wie der Status von `panic` aufgerufen werden kann.
Programs initialize and begin execution from the `main` package. If the `main` package imports other packages, they will be imported in the compile time. If one package is imported many times, it will be only compiled once. After importing packages, programs will initialize the constants and variables within the imported packages, then execute the `init` function if it exists, and so on. After all the other packages are initialized, programs will initialize constants and variables in the `main` package, then execute the `init` function inside the package if it exists. The following figure shows the process.
func erzeugePanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() // Wenn 'f' 'panic' verursacht, wird versucht, dies mit 'recover' zu beheben
return
}
### Die `main` und `init` Funktion
Go hat zwei besondere Funktionen mit dem Namen `main` und `init`, wobei `init` in jedem Paket aufgerufen werden kann und dies bei `main` nur im gleichnahmigen Paket möglich ist. Beide Funktionen besitzen weder Argumente, die übergeben werden können, noch geben sie Werte zurück. Es ist möglich, die `init`-Funktion innerhalb eines Pakets mehrmals aufzurufen, aber ich empfehle jedoch die Nutzung einer einzelnen, da es sonst unübersichtlich werden kann.
Go ruft `init()` und `main()` automatisch auf, sodass dies nicht manuell geschehen muss. Für jedes Paket ist die `init`-Funktion optional, jedoch ist die `main`-Funktion unter `package main` obligatorisch und kann lediglich ein einziges Mal genutzt werden.
Programme werden vom `main`-Paket aus gestarten. Wenn dieses Paket noch weitere Pakete importiert, geschieht dies einzig während der Kompilierung. Nach dem Importieren von Paketen werden zu erst dessen Konstanten und Variablen initialisiert, dann wird `init` aufgerufen usw. Nachdem alle Pakete erfolgreich initialisiert wurden, geschieht all dies im `main`-Paket. Die folgende Grafik illustriert diesen Ablauf.
![](images/2.3.init.png?raw=true)
Figure 2.6 Flow of programs initialization in Go
Abbildung 2.6 Der Initialisierungsablauf eines Programms in Go
### import
We use `import` very often in Go programs as follows.
Oftmals verwenden wir `import` in unseren Go-Programmen wie folgt.
import(
"fmt"
)
Then we use functions in that package as follows.
import(
"fmt"
)
Dann nutzen wir die Funktionen aus dem Paket wie im Beispiel:
fmt.Println("hello world")
`fmt` is from Go standard library, it is located within $GOROOT/pkg. Go supports third-party packages in two ways.
fmt.Println("Hallo Welt")
1. Relative path
import "./model" // load package in the same directory, I don't recommend this way.
2. Absolute path
import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model"
There are some special operators when we import packages, and beginners are always confused by these operators.
`fmt` stammt aus Gos Standardbibliothek, welche sich unter $GOROOT/pkg befindet. Go unterstützt Pakete von Dritten auf zwei Wege.
1. Dot operator.
Sometime we see people use following way to import packages.
import(
. "fmt"
)
The dot operator means you can omit the package name when you call functions inside of that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`.
2. Alias operation.
It changes the name of the package that we imported when we call functions that belong to that package.
import(
f "fmt"
)
Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`.
3. `_` operator.
This is the operator that is difficult to understand without someone explaining it to you.
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
The `_` operator actually means we just want to import that package and execute its `init` function, and we are not sure if want to use the functions belonging to that package.
1. Relativer Pfad
import `./model` // Importiert ein Paket aus dem selben Verzeichnis, jedoch empfehle ich diese Methode nicht.
2. Absoluter Pfad
import `shorturl/model` // Lade das Paket mit dem Pfad "$GOPATH/pkg/shorturl/model"
Es gibt spezielle Operatoren beim Importieren von Paketen, die Anfänger oftmals verwirren.
1. Punkt Operator.
Manchmal sieht man, wie Pakete auf diesem Weg importiert werden.
import(
. "fmt"
)
Der Punkt Operator bedeutet einfach, dass der Name des Paketes beim Aufruf von dessen Funktion weggelassen werden kann. So wird aus `fmt.Printf("Hallo Welt")` einfach `Printf("Hallo Welt")`.
2. Alias Operation.
Dieser verändert den Namen des Paketes, denn wir beim Aufruf von Funktionen angeben müssen.
import(
f "fmt"
)
Aus `fmt.Printf("Hallo Welt")` wird dann `f.Printf("Hallo Welt")`.
3. `_` Operator.
Dieser Operator ist ohne Erklärung ein wenig schwer zu erklären.
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
Der `_` Operator bedeutet einfach nur, das wir das Paket importieren möchten und das dessen `init`-Funktion ausgeführt werden soll. Jedoch sind wir uns nicht sicher, ob wir die anderen Funktionen dieses Pakets überhaupt nutzen wollen.
## Links
- [Directory](preface.md)
- Previous section: [Go foundation](02.2.md)
- Next section: [struct](02.4.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Grundlagen von Go](02.2.md)
- Nächster Abschnitt: [Struct](02.4.md)

View File

@@ -1,214 +1,214 @@
# 2.4 struct
# 2.4 Struct
## struct
## Struct
We can define new types of containers of other properties or fields in Go just like in other programming languages. For example, we can create a type called `person` to represent a person, with fields name and age. We call this kind of type a `struct`.
Wie in anderen Programmiersprachen können wir auch in Go neue Datentypen erstellen, die als eine "Struktur" verknüpfte Eigenschaften oder Felder zusammenfasst. So können wir Beipielsweise einen neuen Datentyp `person` erschaffen, dessen Eingenschaften der Name und das Alter einer Person sind. Wir nennen diesen Datentyp `struct`.
type person struct {
name string
age int
}
Look how easy it is to define a `struct`!
type person struct {
name string
alter int
}
Es sieht ziemlich einfach aus, ein `struct` zu definieren, nicht?
There are two fields.
Es gibt insgesamt zwei Eigenschaften.
- `name` is a `string` used to store a person's name.
- `age` is a `int` used to store a person's age.
- `name` ist ein `string` und wird genutzt, um den Namen einer Person zu speichern.
- `alter` is vom Typ `int` und beinhaltet das Alter einer Person.
Let's see how to use it.
Schauen wir uns das einmal praktisch an.
type person struct {
name string
age int
}
type person struct {
name string
alter int
}
var P person // p is person type
var P person // p ist vom Typ person
P.name = "Astaxie" // assign "Astaxie" to the field 'name' of p
P.age = 25 // assign 25 to field 'age' of p
fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p
P.name = "Astaxie" // Weise "Astaxie" der Eigenschaft 'name' von p zu
P.alter = 25 // Weise 25 der Eigenschaft 'alter' von p zu
fmt.Printf("Der Name der Person lautet %s\n", P.name) // Rufe die Eigenschaft 'name' von p auf
There are three more ways to define a struct.
Es gibt noch drei weitere Wege, ein Struct zu definieren.
- Assign initial values by order
- Weise Startwerte in der Reihenfolge der Eigenschaften zu.
P := person{"Tom", 25}
- Use the format `field:value` to initialize the struct without order
P := person{"Tom", 25}
- Oder nutze das Schema `Eigenschaft:Wert`, um ein Struct zu erstellen, ohne dabei auf die Reihenfolge achten zu müssen.
P := person{age:24, name:"Bob"}
P := person{alter:24, name:"Bob"}
- Define an anonymous struct, then initialize it
- Definiere ein "anonymes" Struct und vergebe Startwerte
P := struct{name string; age int}{"Amy",18}
Let's see a complete example.
P := struct{name string; alter int}{"Amy",18}
Schauen wir uns das vollständige Beispiel an.
package main
import "fmt"
package main
import "fmt"
// define a new type
type person struct {
name string
age int
}
// Definiere einen neuen Datentyp
type person struct {
name string
alter int
}
// compare the age of two people, then return the older person and differences of age
// struct is passed by value
func Older(p1, p2 person) (person, int) {
if p1.age>p2.age {
return p1, p1.age-p2.age
}
return p2, p2.age-p1.age
}
// Vergleiche das Alter von zwei Personen. Dann gibt die ältere Person und den Altersunterschied zurück
// Structs werden als normaler Wert übergeben
func Älter(p1, p2 person) (person, int) {
if p1.alter>p2.alter {
return p1, p1.alter-p2.alter
}
return p2, p2.alter-p1.alter
}
func main() {
var tom person
func main() {
var tom person
// initialization
tom.name, tom.age = "Tom", 18
// Initialisierung
tom.name, tom.alter = "Tom", 18
// initialize two values by format "field:value"
bob := person{age:25, name:"Bob"}
// Initialisiere zwei Werte nach dem Schema "Eigenschaft:Wert"
bob := person{alter:25, name:"Bob"}
// initialize two values with order
paul := person{"Paul", 43}
// Initialisiere die Eigenschaft nach der Reihenfolge
paul := person{"Paul", 43}
tb_Older, tb_diff := Older(tom, bob)
tp_Older, tp_diff := Older(tom, paul)
bp_Older, bp_diff := Older(bob, paul)
tb_Älter, tb_diff := Älter(tom, bob)
tp_Älter, tp_diff := Älter(tom, paul)
bp_Älter, bp_diff := Älter(bob, paul)
fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff)
fmt.Printf("Von %s und %s ist %s um %d Jahre älter\n", tom.name, bob.name, tb_Älter.name, tb_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff)
fmt.Printf("Von %s und %s ist %s um %d Jahre älter\n", tom.name, paul.name, tp_Älter.name, tp_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff)
}
### embedded fields in struct
fmt.Printf("Von %s und %s ist %s um %d Jahre älter\n", bob.name, paul.name, bp_Älter.name, bp_diff)
}
### Eigenschaften in Structs einbetten
I've just introduced to you how to define a struct with field names and type. In fact, Go supports fields without names, but with types. We call these embedded fields.
Soeben habe ich Dir gezeigt, wie Du ein Struct mit Eigenschaften und Datentypen definieren kannst. Aber Go unterstützt auch Eigenschaften, die keinen Namen besitzen und nur dessen Datentyp angegeben wird. Wir bezeichnen diese als eingebettete Eigenschaften.
When the embedded field is a struct, all the fields in that struct will implicitly be the fields in the struct in which it has been embdedded.
Wenn die eingebette Eigenschaft ein weiteres Struct ist, dann all seine Eigenschaften in den Struct übertragen, in dem es eingebettet wurde.
Let's see one example.
Betrachten wir ein Beispiel.
package main
import "fmt"
package main
import "fmt"
type Human struct {
name string
age int
weight int
}
type Mensch struct {
name string
alter int
gewicht int
}
type Student struct {
Human // embedded field, it means Student struct includes all fields that Human has.
specialty string
}
type Student struct {
Mensch // Eingebettete Eigenschaft, d.h. Student besitzt alle Eigenschaften von Mensch.
fachgebiet string
}
func main() {
// initialize a student
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
func main() {
// Initialisierng eines Studenten
mark := Student{Mensch{"Mark", 25, 120}, "Informatik"}
// access fields
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His specialty is ", mark.specialty)
// modify notes
mark.specialty = "AI"
fmt.Println("Mark changed his specialty")
fmt.Println("His specialty is ", mark.specialty)
// modify age
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// modify weight
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}
// Eigenschaften aufrufen
fmt.Println("Sein Name lautet ", mark.name)
fmt.Println("Sein Alter ist ", mark.alter)
fmt.Println("Er wiegt ", mark.gewicht)
fmt.Println("Sein Fachgebiet ist ", mark.fachgebiet)
// Eigenschaften verändern
mark.fachgebiet = "Künstliche Intelligenz"
fmt.Println("Mark hat sein Fachgebiet gewechselt")
fmt.Println("Sein Fachgebiet lautet ", mark.fachgebiet)
// Alter ändern
fmt.Println("Mark wurde alt")
mark.alter = 46
fmt.Println("Sein Alter beträgt ", mark.alter, " Jahre")
// Gewicht ändern
fmt.Println("Mark ist kein Athlet mehr")
mark.gewicht += 60
fmt.Println("Er wiegt nun", mark.gewicht)
}
![](images/2.4.student_struct.png?raw=true)
Figure 2.7 Inheritance in Student and Human
Abbildung 2.7 Einbettung von Mensch in Student
We see that we can access the age and name fields in Student just like we can in Human. This is how embedded fields work. It's very cool, isn't it? Hold on, there's something cooler! You can even use Student to access Human in this embedded field!
Wie Du siehst, können wir die Eigenschaften `alter` und `name` vom Typ Student genauso aufrufen, wie jene vom Typ Mensch. Genau so funktioniert das Einbetten. Ziemlich einfach, nicht wahr? Aber es gibt noch Kleinigkeit, die ich Dir zeigen möchte. Du kannst auch den Typ Mensch verwenden, um auf Eigenschaften von Student zurückzugreifen.
mark.Human = Human{"Marcus", 55, 220}
mark.Human.age -= 1
All the types in Go can be used as embedded fields.
mark.Mensch = Mensch{"Marcus", 55, 220}
mark.Mensch.alter -= 1
In Go können alle Datenttypen eingebettet werden.
package main
import "fmt"
package main
import "fmt"
type Skills []string
type Fähigkeiten []string
type Human struct {
name string
age int
weight int
}
type Mensch struct {
name string
alter int
gewicht int
}
type Student struct {
Human // struct as embedded field
Skills // string slice as embedded field
int // built-in type as embedded field
specialty string
}
type Student struct {
Mensch // Struct als eingebettete Eigenschaft
Fähigkeiten // Slice vom Typ string als eingebettete Eigenschaft
int // Standard Datentyp als eingebettete Eigenschaft
fachgebiet string
}
func main() {
// initialize Student Jane
jane := Student{Human:Human{"Jane", 35, 100}, specialty:"Biology"}
// access fields
fmt.Println("Her name is ", jane.name)
fmt.Println("Her age is ", jane.age)
fmt.Println("Her weight is ", jane.weight)
fmt.Println("Her specialty is ", jane.specialty)
// modify value of skill field
jane.Skills = []string{"anatomy"}
fmt.Println("Her skills are ", jane.Skills)
fmt.Println("She acquired two new ones ")
jane.Skills = append(jane.Skills, "physics", "golang")
fmt.Println("Her skills now are ", jane.Skills)
// modify embedded field
jane.int = 3
fmt.Println("Her preferred number is", jane.int)
}
In the above example, we can see that all types can be embedded fields and we can use functions to operate on them.
func main() {
// Initialisiere Studentin Jane
jane := Student{Mensch:Mensch{"Jane", 35, 100}, fachgebiet:"Biologie"}
// Eigenschaften aufrufen
fmt.Println("Ihr Name lautet ", jane.name)
fmt.Println("Ihr Alter ist ", jane.alter)
fmt.Println("Sie wiegt ", jane.gewicht)
fmt.Println("Ihr Fachgebiet ist ", jane.fachgebiet)
// Änderung des Wertes der Eigenschaft fähigkeit
jane.Fähigkeiten = []string{"Anatomie"}
fmt.Println("Sie besitzt folgende Fähigkeiten: ", jane.Fähigkeiten)
fmt.Println("Sie hat zwei neue Fähigkeiten erworben ")
jane.Fähigkeiten = append(jane.Fähigkeiten, "Physik", "Golang")
fmt.Println("Sie besitzt nun folgende Fähigkeiten ", jane.Fähigkeiten)
// Veränderung einer eingebetteten Eigenschaft
jane.int = 3
fmt.Println("Ihre bevorzugte Nummer lautet", jane.int)
}
Im oberen Beispiel ist erkenntlich, dass alle Datentypen eingebettet werden und Funktion auf ihre Werte zugreifen können.
There is one more problem however. If Human has a field called `phone` and Student has a field with same name, what should we do?
Aber es gibt noch ein kleines Problem. Was geschieht, wenn Mensch die Eigenschaft `telefon` besitzt und Student eine Eigenschaft mit dem gleichen Namen besitzt?
Go use a very simple way to solve it. The outer fields get upper access levels, which means when you access `student.phone`, we will get the field called phone in student, not the one in the Human struct. This feature can be simply seen as field `overload`ing.
Go nutzt einen einfachen Weg zur Unterscheidung. Um die Eigenschaft `telefon` von Student zu erhalten, nutzt Du weiterhein `Student.telefon`. Die gleichnahmige Eigenschaft von Mensch kannst Du erhalten, indem Du `Student.Mensch.telefon` verwendest. Wie Du siehst, muss einfach der eingebette Datentyp vorangestellt werden. Diese Fähigkeiten wird "überladen" genannt (im Englischen `overloading`).
package main
import "fmt"
package main
import "fmt"
type Human struct {
name string
age int
phone string // Human has phone field
}
type Mensch struct {
name string
alter int
telefon string // Mensch hat die Eigenschaft telefon
}
type Employee struct {
Human // embedded field Human
specialty string
phone string // phone in employee
}
type Mitarbeiter struct {
Mensch // Eingebetter Datentyp Mensch
spezialisierung string
telefon string // Eigenschaft telefon in Mitarbeiter
}
func main() {
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
fmt.Println("Bob's work phone is:", Bob.phone)
// access phone field in Human
fmt.Println("Bob's personal phone is:", Bob.Human.phone)
}
func main() {
Bob := Mitarbeiter{Mensch{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
fmt.Println("Bobs berufliche Telefonnummer lautet ", Bob.telefon)
// Greife auf Eigenschaft telefon in Mensch zu
fmt.Println("Bobs private Telefonnummer lauet ", Bob.Mensch.telefon)
}
## Links
- [Directory](preface.md)
- Previous section: [Control statements and functions](02.3.md)
- Next section: [Object-oriented](02.5.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Kontrollstrukturen und Funktionen](02.3.md)
- Nächster Abschnitt: [Objektorientiertes Programmieren](02.5.md)

View File

@@ -1,307 +1,309 @@
# Object-oriented
# 2.5 Objektorientierte Programmierung
We talked about functions and structs in the last two sections, but did you ever consider using functions as fields of a struct? In this section, I will introduce you to another form of function that has a receiver, which is called `method`.
In den letzen beiden Abschnitten hatten wir uns mit Funktionen und Structs beschäftigt, aber hast Du jemals daran gedacht, Funktionen als Eigenschaft in einem Struct zu verwenden? In diesem Abschnitt werden ich Dir eine besondere Art von Funktionen vorstellen, die einen Reciever (engl. to recieve - empfangen) besitzen. Sie werden auch `Methoden` genannt.
## method
## Methoden
Suppose you define a "rectangle" struct and you want to calculate its area. We'd typically use the following code to achieve this goal.
Sagen wir, Du definierst einen Struct mit dem Namen "Rechteck" und möchtest die Fläche ausrechnen. Normalerweise würden wir folgenden Code verwenden, um dies zu bewerkstelligen.
package main
import "fmt"
package main
import "fmt"
type Rectangle struct {
width, height float64
}
type Rechteck struct {
breite, höhe float64
}
func area(r Rectangle) float64 {
return r.width*r.height
}
func Fläche(r Rechteck) float64 {
return r.breite*r.höhe
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
fmt.Println("Area of r1 is: ", area(r1))
fmt.Println("Area of r2 is: ", area(r2))
}
The above example can calculate a rectangle's area. We use the function called `area`, but it's not a method of the rectangle struct (like class methods in classic object-oriented languages). The function and struct are two independent things as you may notice.
func main() {
r1 := Rechteck{12, 2}
r2 := Rechteck{9, 4}
fmt.Println("Fläche von r1: ", Fläche(r1))
fmt.Println("Fläche von r2: ", Fläche(r2))
}
Das obere Beispiel kann die Fläche eines Rechtecks ermitteln. Dafür haben wir die Funktion `Fläche()` verwendet, aber es ist keine Methode des Structs Rechteck (welche mit einer Klasse in klassischen objektorientierten Sprachen wären). Die Funktion und der Struct sind voneinander unabhängig.
It's not a problem so far. However, if you also have to calculate the area of a circle, square, pentagon, or any other kind of shape, you are going to need to add additional functions with very similar names.
Soweit ist dies noch kein Problem. Wenn Du aber den Flächeninhalt eines Kreies, Quadrahts, Fünfecks oder einer anderen Form berechnen musst, brauchst Du dafür Funktionen mit einem sehr ähnlichen Namen,
![](images/2.5.rect_func_without_receiver.png?raw=true)
Figure 2.8 Relationship between function and struct
Abbildung 2.8 Beziehung zwischen Funktionen und Structs
Obviously that's not cool. Also, the area should really be the property of a circle or rectangle.
Offensichtlich ist dies nicht sehr praktisch. Auch sollte die Fläche eine Eigenschaft des Kreises oder Rechtecks sein.
For those reasons, we have the `method` concept. `method` is affiliated with type. It has the same syntax as functions do except for an additional parameter after the `func` keyword called the `receiver`, which is the main body of that method.
Aus diesem Grund gibt es das Konzept der `Methoden`. Diese sind immer mit einem Datentyp verbunden. Sie haben den selben Syntax wie normale Funktionen, jedoch besitzen sie einen weiteren Parameter nach dem Schlüsselwort `func`, den `Reciever`.
Using the same example, `Rectangle.area()` belongs directly to rectangle, instead of as a peripheral function. More specifically, `length`, `width` and `area()` all belong to rectangle.
Im selben Beispiel würde mit `Rechteck.Fläche()` die Methode des Structs Rechteck aufgerufen. Somit gehören `länge`, `breite` und `Fläche()` zum Struct.
As Rob Pike said.
Wie Rob Pike sagte:
"A method is a function with an implicit first argument, called a receiver."
Syntax of method.
"A method is a function with an implicit first argument, called a receiver."
"Eine Methode ist eine Funktion mit einem ersten, impliziten Argument, welches Reciever genannt wird."
func (r ReceiverType) funcName(parameters) (results)
Let's change our example using `method` instead.
Syntax einer Methode.
package main
import (
"fmt"
"math"
)
func (r RecieverTyp) funcName(Parameter) (Ergebnis)
Lass uns das Beispiel von vorhin umschreiben.
type Rectangle struct {
width, height float64
}
package main
import (
"fmt"
"math"
)
type Circle struct {
radius float64
}
type Rechteck struct {
breite, höhe float64
}
func (r Rectangle) area() float64 {
return r.width*r.height
}
type Kreis struct {
radius float64
}
func (c Circle) area() float64 {
return c.radius * c.radius * math.Pi
}
func (r Rechteck) fläche() float64 {
return r.breite*r.höhe
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
c1 := Circle{10}
c2 := Circle{25}
func (c Kreis) fläche() float64 {
return c.radius * c.radius * math.Pi
}
fmt.Println("Area of r1 is: ", r1.area())
fmt.Println("Area of r2 is: ", r2.area())
fmt.Println("Area of c1 is: ", c1.area())
fmt.Println("Area of c2 is: ", c2.area())
}
Notes for using methods.
func main() {
r1 := Rechteck{12, 2}
r2 := Rechteck{9, 4}
c1 := Kreis{10}
c2 := Kreis{25}
- If the name of methods are the same but they don't share the same receivers, they are not the same.
- Methods are able to access fields within receivers.
- Use `.` to call a method in the struct, the same way fields are called.
fmt.Println("Fläche von r1 ist: ", r1.fläche())
fmt.Println("Fläche von r2 ist: ", r2.fläche())
fmt.Println("Fläche von c1 ist: ", c1.fläche())
fmt.Println("Fläche von c2 ist: ", c2.fläche())
}
Anmerkungen zu der Nutzung von Methoden.
- Beide Methoden haben den selben Namen, gehören jedoch verschiedenen Recievern an.
- Methoden können auf die Eigenschaften des Recievers zugreifen.
- Nutze `.`, um Methoden wie Eigenschaften aufzurufen.
![](images/2.5.shapes_func_with_receiver_cp.png?raw=true)
Figure 2.9 Methods are different in different structs
Abbildung 2.9 Die Methoden sind in jedem Struct verschieden
In the example above, the area() methods belong to both Rectangle and Circle respectively, so the receivers are Rectangle and Circle.
Im oberen Beispiel ist die Methode `Fläche()` für Rechteck und Kreis zugänglich, sodass sie zwei verschiedene Reciever besitzt.
One thing that's worth noting is that the method with a dotted line means the receiver is passed by value, not by reference. The difference between them is that a method can change its receiver's values when the receiver is passed by reference, and it gets a copy of the receiver when the receiver is passed by value.
Bei der Nutzung von Methoden werden dieser Kopien der Werte eines Structs übergeben. `Fläche()` bekommt also nicht einen Zeiger zum Original übergeben, sondern nur eine Kopie dessen. Somit kann das Orginal nicht verändert werden. Ein `*` vor dem Reciver übergibt dageben den Zeiger und erlaubt damit auch Zugriff auf das Original.
Can the receiver only be a struct? Of course not. Any type can be the receiver of a method. You may be confused about customized types. Struct is a special kind of customized type -there are more customized types.
Können Reciever nur in Verbindung mit Structs genutzt werden? Natürlich nicht. Jeder Datentyp kann als Reciever fungieren. Dies wird Dir vielleicht bei selbstdefinierten Datentypen etwas komisch vorkommen, aber mit den Structs haben wir das Selbe gemacht - einen eigenen Datentypen erstellt.
Use the following format to define a customized type.
Das Folgende Schema kann genutzt werden, um einen neuen Datentypen zu definieren.
type typeName typeLiteral
Examples of customized types:
type Name Datentyp
Beispiele für selbstdefinierte Datentypen:
type ages int
type alter int
type money float32
type geld float32
type months map[string]int
type monate map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}
I hope that you know how to use customized types now. Similar to `typedef` in C, we use `ages` to substitute `int` in the above example.
m := monate {
"Januar":31,
"Februar":28,
...
"Dezember":31,
}
Ich hoffe, dass Du die Definition eigener Datentypen jetzt verstanden hast. Es ist `typedef` aus C sehr ähnlich. Im oberen Beispiel ersetzen wir `int` einfach durch `alter`.
Let's get back to talking about `method`.
Kommen wir zurück zu unseren `Methoden`.
You can use as many methods in custom types as you want.
Du kannst soviele Datentypen und dazugehörige Methoden erstellen, wie Du willst.
package main
import "fmt"
package main
import "fmt"
const(
WHITE = iota
BLACK
BLUE
RED
YELLOW
)
const(
WEISS = iota
SCHWARZ
BLAU
ROT
GELB
)
type Color byte
type Farbe byte
type Box struct {
width, height, depth float64
color Color
}
type Box struct {
breite, höhe, tiefe float64
farbe Farbe
}
type BoxList []Box //a slice of boxes
type BoxListe []Box // Ein Slice mit Elementen vom Typ Box
func (b Box) Volume() float64 {
return b.width * b.height * b.depth
}
func (b Box) Volumen() float64 {
return b.breite * b.höhe * b.tiefe
}
func (b *Box) SetColor(c Color) {
b.color = c
}
func (b *Box) SetzeFarbe(c Farbe) {
b.farbe = c
}
func (bl BoxList) BiggestsColor() Color {
v := 0.00
k := Color(WHITE)
for _, b := range bl {
if b.Volume() > v {
v = b.Volume()
k = b.color
}
}
return k
}
func (bl BoxListe) HöchsteFarbe() Farbe {
v := 0.00
k := Farbe(WEISS)
for _, b := range bl {
if b.Volumen() > v {
v = b.Volumen()
k = b.farbe
}
}
return k
}
func (bl BoxList) PaintItBlack() {
for i, _ := range bl {
bl[i].SetColor(BLACK)
}
}
func (bl BoxListe) MaleAlleSchwarzAn() {
for i, _ := range bl {
bl[i].SetzeFarbe(SCHWARZ)
}
}
func (c Color) String() string {
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
return strings[c]
}
func (c Farbe) String() string {
strings := []string {"WEISS", "SCHWARZ", "BLAU", "ROT", "GELB"}
return strings[c]
}
func main() {
boxes := BoxList {
Box{4, 4, 4, RED},
Box{10, 10, 1, YELLOW},
Box{1, 1, 20, BLACK},
Box{10, 10, 1, BLUE},
Box{10, 30, 1, WHITE},
Box{20, 20, 20, YELLOW},
}
func main() {
boxes := BoxListe {
Box{4, 4, 4, ROT},
Box{10, 10, 1, GELB},
Box{1, 1, 20, SCHWARZ},
Box{10, 10, 1, BLAU},
Box{10, 30, 1, WEISS},
Box{20, 20, 20, GELB},
}
fmt.Printf("We have %d boxes in our set\n", len(boxes))
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Printf("Wir haben %d Boxen in unserer Liste\n", len(boxes))
fmt.Println("Das Volumen der Ersten beträgt ", boxes[0].Volumen(), "cm³")
fmt.Println("Die Farbe der letzen Box ist",boxes[len(boxes)-1].farbe.String())
fmt.Println("Die größte Box ist ", boxes.HöchsteFarbe().String())
fmt.Println("Let's paint them all black")
boxes.PaintItBlack()
fmt.Println("The color of the second one is", boxes[1].color.String())
fmt.Println("Malen wir sie alle schwarz an")
boxes.MaleAlleSchwarzAn()
fmt.Println("Die Farbe der zweiten Box lautet ", boxes[1].farbe.String())
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
}
We define some constants and customized types.
fmt.Println("Offensichtlich ist die größte Box ", boxes.HöchsteFarbe().String())
}
Wir hatten ein paar Konstanten und selbstdefinierte Datentypen erstellt.
- Use `Color` as alias of `byte`.
- Define a struct `Box` which has fields height, width, length and color.
- Define a struct `BoxList` which has `Box` as its field.
- Nutze `Farbe` als Alias für `byte`.
- Wir definierten den Struct `Box` mit den Eigenschaften breite, länge, tiefe und farbe.
- Wir definierten einen Slice `BoxListe` mit Elementen vom Typ `Box`.
Then we defined some methods for our customized types.
Dann haben wir Methoden für unsere selbsterstellten Datentypen hinzugefügt.
- Volume() uses Box as its receiver and returns volume of Box.
- SetColor(c Color) changes Box's color.
- BiggestsColor() returns the color which has the biggest volume.
- PaintItBlack() sets color for all Box in BoxList to black.
- String() use Color as its receiver, returns the string format of color name.
- Volumen() nutzt Box als Reciever und gibt das Volumen einer Box.
- SetzeFarbe(c Farbe) ändert die Farbe einer Box.
- GrößteFarbe() gibt die Box mit dem größten Volumen zurück.
- MaleAlleSchwarzAn() setzt die Farbe aller Boxen auf schwarz.
- String() benutzt Farbe als Reciever und gibt den Farbnamen als String zurück.
Is it much clearer when we use words to describe our requirements? We often write our requirements before we start coding.
Ist es nicht einfacher, Wörter zum Beschreiben unserer Anforderungen zu benutzen? Oftmals definieren wir unsere Anforderungen schon vor dem Programmieren.
### Use pointer as receiver
### Zeiger als Reciever
Let's take a look at `SetColor` method. Its receiver is a pointer of Box. Yes, you can use `*Box` as a receiver. Why do we use a pointer here? Because we want to change Box's color in this method. Thus, if we don't use a pointer, it will only change the value inside a copy of Box.
Werfen wir einen näheren Blick auf die Methode `SetzeFarbe()`. Ihr Reciever ist der Zeiger mit dem Verweis zu einer Box. Warum benutzen wir hier einen Zeiger? Wie bereits erwähnt, erhälst Du mit `*Box` Zugriff auf das Original und kannst es somit ändern. Nützten wir keinen Zeiger, so hätte die Methode nur eine Kopie des Wertes übergeben bekommen.
If we see that a receiver is the first argument of a method, it's not hard to understand how it works.
Wenn wir einen Reciever als ersten Parameter einer Methode sehen, dürfte ihr Zweck leicht zu verstehen sein.
You might be asking why we aren't using `(*b).Color=c` instead of `b.Color=c` in the SetColor() method. Either one is OK here because Go knows how to interpret the assignment. Do you think Go is more fascinating now?
Du fragst Dich bestimmt, warum wir nicht einfach `(*b).Farbe=c` verwenden, statt `b.Color=c` in der `SetzeFarbe()` Methode. Beide Wege sind OK und Go weiß die erste Zuweisung zu interpretieren. Findest Du Go nun nicht auch faszinierend?
You may also be asking whether we should use `(&bl[i]).SetColor(BLACK)` in `PaintItBlack` because we pass a pointer to `SetColor`. Again, either one is OK because Go knows how to interpret it!
Des Weiteren fragst Du dich vielleicht auch, warum wir nicht `(&bl[i]).SetzeFarbe(BLACK)` in `MaleAlleSchwarzAn()` nutzen, so wie es in `SetzeFarbe()` der Fall ist. Nochmals, beide Varianten sind OK und Go weiß damit umzugehen.
### Inheritance of method
### Vererbung von Methoden
We learned about inheritance of fields in the last section. Similarly, we also have method inheritance in Go. If an anonymous field has methods, then the struct that contains the field will have all the methods from it as well.
Wir haben die Vererbung bzw. das Einbetten von Eigengeschaften bereits im letzen Abschnitt kennengelernt. Ähnlich funktioniert auch das Einbetten von Methoden. Wenn ein Struct eigene Methoden hat und es in ein weiteres Struct eingebettet wird, so werden die Methoden wie die Eigenschaften mit eingebettet, also vererbt.
package main
import "fmt"
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Mensch struct {
name string
alter int
telefon string
}
type Student struct {
Human // anonymous field
school string
}
type Student struct {
Mensch // Eingebetter Struct als Eigenschaft ohne Namen
schule string
}
type Employee struct {
Human
company string
}
type Mitarbeiter struct {
Mensch
unternehmen string
}
// define a method in Human
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Definiere eine Methode für Mensch
func (h *Mensch) SagHallo() {
fmt.Printf("Hallo, ich bin %s. Du erreichst mich unter %s\n", h.name, h.telefon)
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
func main() {
mark := Student{Mensch{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Mitarbeiter{Mensch{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
mark.SagHallo()
sam.SagHallo()
}
### Method overload
### Das Überladen von Methoden
If we want Employee to have its own method `SayHi`, we can define a method that has the same name in Employee, and it will hide `SayHi` in Human when we call it.
Wenn wir für Mitarbeiter eine eigene Methode `SagHallo()` erstellen wollen, wird die Methode `SagHallo()` von Mensch durch die von Mitarbeiter überladen.
package main
import "fmt"
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Mensch struct {
name string
alter int
telefon string
}
type Student struct {
Human
school string
}
type Student struct {
Mensch
schule string
}
type Employee struct {
Human
company string
}
type Mitarbeiter struct {
Mensch
unternehmen string
}
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func (h *Mensch) SagHallo() {
fmt.Printf("Hallo, ich bin %s und Du erreicht mich unter %s\n", h.name, h.telefon)
}
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
func (e *Mitarbeiter) SagHallo() {
fmt.Printf("Hallo, ich bin %s, arbeite bei %s. Du erreicht mich unter %s\n", e.name,
e.unternehmen, e.telefon) // Du kannst die Argumente auch auf zwei Zeilen verteilen.
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
func main() {
mark := Student{Mensch{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Mitarbeiter{Mensch{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
You are able to write an Object-oriented program now, and methods use rule of capital letter to decide whether public or private as well.
mark.SagHallo()
sam.SagHallo()
}
Nun bist Du bereit, Dein eigenes, objektorientiers Programm zu schreiben. Auch Methoden unterliegen der Regel, dass die Groß- und Kleinschreibung des ersten Buchstaben über die Sichtbarkeit (öffentlich oder privat) entscheidet.
## Links
- [Directory](preface.md)
- Previous section: [struct](02.4.md)
- Next section: [interface](02.6.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Struct](02.4.md)
- Nächster Abschnitt: [Interface](02.6.md)

View File

@@ -1,199 +1,199 @@
# 2.6 Interface
# 2.6 Interfaces
## Interface
One of the subtlest design features in Go are interfaces. After reading this section, you will likely be impressed by their implementation.
Eines der besten Sprachmerkmale von Go sind Interfaces. Nach dem Lesen dieses Abschnitts wirst Du über dessen Implementation staunen.
### What is an interface
### Was ist ein Interface
In short, an interface is a set of methods that we use to define a set of actions.
Kurz gesagt, ein Interface dient der Zusammenfassung von Funktionen, die durch ihren ähnlichen Aufbau eine Beziehung zueinander haben.
Like the examples in previous sections, both Student and Employee can `SayHi()`, but they don't do the same thing.
Wie im Beispiel aus dem letzten Abschnitt haben Student und Mitarbeiter hier beide die Methode `SagHallo()`, welche sich ähnlich, aber nicht gleich verhalten.
Let's do some more work. We'll add one more method `Sing()` to them, along with the `BorrowMoney()` method to Student and the `SpendSalary()` method to Employee.
Machen wir uns wieder an die Arbeit, indem wir beiden Structs die Methode `Singen()` hinzufügen. Zudem erweitern wir Student um die Methode `GeldLeihen()` und Mitarbeiter um die Methode `GehaltAusgeben()`.
Now, Student has three methods called `SayHi()`, `Sing()` and `BorrowMoney()`, and Employee has `SayHi()`, `Sing()` and `SpendSalary()`.
Nun besitzt Student die drei Methoden `SagHallo()`, `Singen()` und `GeldLeihen()` und Mitarbeiter `SagHallo()`, `Singen()` sowie `GehaltAusgeben()`.
This combination of methods is called an interface and is implemented by both Student and Employee. So, Student and Employee implement the interface: `SayHi()` and `Sing()`. At the same time, Employee doesn't implement the interface: `SayHi()`, `Sing()`, `BorrowMoney()`, and Student doesn't implement the interface: `SayHi()`, `Sing()`, `SpendSalary()`. This is because Employee doesn't have the method `BorrowMoney()` and Student doesn't have the method `SpendSalary()`.
Diese Kombination von Methoden wird Interface genannt und umfasst sowohl Student als auch Mitarbeiter. Somit besitzen beide Datentypen die Interfaces `SagHallo()` und `Singen()`. Jedoch implementieren beide Datentypen keine gemeinsames Interfaces für `GeldLeihen()` und `GehaltAusgeben()`, da diese Methoden nicht für beide Structs definiert wurden.
### Type of Interface
### Interface als Datentyp
An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface.
Ein Interface definiert eine Liste von Methoden. Besitzt ein Datentyp alle Methoden, die das Interface definiert, so umfasst das Interface diesen Datentypen. Schauen wir uns ein Beispiel zur Verdeutlichung an.
type Human struct {
name string
age int
phone string
type Mensch struct {
name string
alter int
telefon string
}
type Student struct {
Human
school string
loan float32
Mensch
schule string
kredit float32
}
type Employee struct {
Human
company string
money float32
type Mitarbeiter struct {
Mensch
unternehmen string
geld float32
}
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
func (m *Mensch) SagHallo() {
fmt.Printf("Hallo, ich bin %s und Du erreichst mich unter %s\n", m.name, m.telefon)
}
func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
func (m *Mensch) Singen(liedtext string) {
fmt.Println("La la, la la la, la la la la la...", liedtext)
}
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
func (m *Mensch) SichBetrinken(bierkrug string) {
fmt.Println("schluck schluck schluck...", bierkrug)
}
// Employee overloads Sayhi
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
// Mitarbeiter "überlädt" SagHallo
func (m *Mitarbeiter) SagHallo() {
fmt.Printf("Hallo, ich bin %s und arbeite bei %s. Ruf mich unter der Nummer %s an\n", m.name,
m.unternehmen, m.telefon) // Du kannst die Argumente auch auf zwei Zweilen aufteilen.
}
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount // (again and again and...)
func (s *Student) GeldLeihen(betrag float32) {
s.kredit += betrag // (immer und immer wieder...)
}
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount // More vodka please!!! Get me through the day!
func (m *Mitarbeiter) GehaltAusgeben(betrag float32) {
m.geld -= betrag // Einen Vodka bitte!!! Bring mich durch den Tag!
}
// define interface
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
// Das Interface definieren
type Männer interface {
SagHallo()
Singe(liedtext string)
SichBetrinken(bierkrug string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
type JungerMann interface {
SagHallo()
Singe(liedtext string)
GeldLeihen(betrag float32)
}
type ElderlyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
type Greis interface {
SagHallo()
Singe(liedtext string)
GeldAusgeben(betrag float32)
}
We know that an interface can be implemented by any type, and one type can implement many interfaces simultaneously.
Wir wissen, dass ein Interface von jedem Datentypen implementiert werden und ein Datentyp viele Interfaces umfassen kann.
Note that any type implements the empty interface `interface{}` because it doesn't have any methods and all types have zero methods by default.
Zudem implementiert jeder Datentyp das leere Interface `interface{}`, da es keine Methoden definiert und alle Datentypen von Beginn an keine Methoden besitzen.
### Value of interface
### Interface als Datentyp
So what kind of values can be put in the interface? If we define a variable as a type interface, any type that implements the interface can assigned to this variable.
Welche Arten von Werten können mit einem Interface verknüpft werden? Wen wir eine Variable vom Typ Interface definieren, dann kann jeder Datentyp, der das Interface implementiert wird, der Variable zugewiesen werden.
Like the above example, if we define a variable "m" as interface Men, then any one of Student, Human or Employee can be assigned to "m". So we could have a slice of Men, and any type that implements interface Men can assign to this slice. Be aware however that the slice of interface doesn't have the same behavior as a slice of other types.
Es ist wie im oberen Beispiel. Erstellen wir eine Variable "m" mit dem Interface Männer, kann jeder Student, Mensch oder Mitarbeiter "m" zugewiesen werden. So könnten wir ein Slice mit dem Interface Männer jeden Datentyp hinzufügen, der ebenfalls das Interface Männer implementiert. Bedenke aber, dass sich das Verhalten von Slices ändert, wenn dies Elemente eines Interface statt eines Datentypes verwendet.
package main
import "fmt"
type Human struct {
name string
age int
phone string
type Mensch struct {
name string
alter int
telefon string
}
type Student struct {
Human
school string
loan float32
Mensch
schule string
geld float32
}
type Employee struct {
Human
company string
money float32
type Mitarbeiter struct {
Mensch
unternehmen string
geld float32
}
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
func (m Mensch) SagHallo() {
fmt.Printf("Hallo, ich bin %s und Du erreicht mich unter %s\n", m.name, m.telefon)
}
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
func (m Mensch) Singe(liedtext string) {
fmt.Println("La la la la...", liedtext)
}
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
func (m Mitarbeiter) SagHallo() {
fmt.Printf("Hallo, ich bin %s und arbeite bei %s. Rufe mich unter der Nummer %s an\n", m.name,
m.unternehmen, m.telefon) // Du kannst die Argumente auch auf zwei Zweilen aufteilen.
}
// Interface Men implemented by Human, Student and Employee
type Men interface {
SayHi()
Sing(lyrics string)
// Das Interface Männer wird von Mensch, Student und Mitarbeiter implementiert
type Männer interface {
SagHallo()
Singe(liedtext string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
mike := Student{Mensch{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Mensch{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Mitarbeiter{Mensch{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
Tom := Mitarbeiter{Mensch{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
// define interface i
var i Men
// Definiere i vom Typ Interface Männer
var i Männer
//i can store Student
// i kann Studenten zugewiesen bekommen
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
fmt.Println("Das ist Mike, ein Student:")
i.SagHallo()
i.Singe("November rain")
//i can store Employee
// i kann auch Mitarbeiter zugewiesen bekommen
i = Tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
fmt.Println("Das ist Tom, ein Mitarbeiter:")
i.SagHallo()
i.Singe("Born to be wild")
// slice of Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
// these three elements are different types but they all implemented interface Men
// Slice mit Männern
fmt.Println("Nutzen wir einen Slice vom Typ Männer und schauen, was passiert")
x := make([]Männer, 3)
// Alle drei Variablen haben verschiedene Datentypen, implentieren aber das selbe Interface
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x {
value.SayHi()
for _, wert := range x {
wert.SagHallo()
}
}
An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself.
Ein Interface ist eine Ansammlung von abstrakten Methoden, das jeder Datentypen implementieren kann, die noch nicht Teil des Interfaces sind. Daher kann es sich nicht selbst implemetieren
### Empty interface
### Leeres Interface
An empty interface is an interface that doesn't contain any methods, so all types implement an empty interface. This fact is very useful when we want to store all types at some point, and is similar to void* in C.
Ein leeres Interfaces umfasst keine Methoden, sodass alle Datentypen dieses Interface implementieren. Dies ist sehr nützlich, wenn wir irgendwann alle Datentypen speichern möchten. Es ist void* aus C sehr ähnlich.
// define a as empty interface
// Definition eines leeren Interfaces
var a interface{}
var i int = 5
s := "Hello world"
// a can store value of any type
s := "Hallo Welt"
// a kann jeder Datentyp zugewiesen werden
a = i
a = s
If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty interface as its return value type, it can return any type.
Wenn eine Funktion ein leeres Interface als Argumenttyp verwendet, wird jeder Datentyp akzeptiert. Gleiches gilt für den Rückgabewert einer Funktion.
### Method arguments of an interface
### Ein Interface als Methodenargument
Any variable can be used in an interface. So how can we use this feature to pass any type of variable to a function?
Jede Variable kann mit einem Interface genutzt werden. Aber wie können wir diese Eigenschaft nutzen, um einen beliebigen Datentyp einer Funktion zu übergeben?
For example we use fmt.Println a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of fmt, we see the following definition.
Zum Beispiel nutzen wir `fmt.Println` sehr oft, aber hast Du jemals gemerkt, dass jeder Datentyp verwendet werden kann? Werfen wir mal einen Blick auf den open-source Code von `fmt`. Wir sehen die folgende Definition der Funktion.
type Stringer interface {
String() string
}
This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it.
Dies bedeutet, dass jeder Datentyp, der das Interface Stringer implementiert, `fmt.Println` übergeben werden kann. Beweisen wir es.
package main
@@ -202,41 +202,42 @@ This means any type that implements interface Stringer can be passed to fmt.Prin
"strconv"
)
type Human struct {
name string
age int
phone string
type Mensch struct {
name string
alter int
telefon string
}
// Human implemented fmt.Stringer
func (h Human) String() string {
return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone
// Mensch implementiert fmt.Stringer
func (m Mensch) String() string {
return "Name:" + m.name + ", Alter:" + strconv.Itoa(m.alter) + " Jahre, Kontakt:" + m.telefon
}
func main() {
Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob)
Bob := Mensch{"Bob", 39, "000-7777-XXX"}
fmt.Println("Dieser Mensch ist: ", Bob)
}
Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format.
Werfen wir nochmal einen Blick auf das Beispiel mit den Boxen von vorhin. Du wirst feststellen, dass der Datentyp Farbe ebenfalls das Interface Stringer definiert, sodass wir die Ausgabe formatieren können. Würden wir dies nicht tun, nutzt `fmt.Println()` die Standardformatierung.
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())
fmt.Println("Die größte Box ist", boxen.HöchsteFarbe().String())
fmt.Println("Die größte Box ist", boxen.HöchsteFarbe())
Attention: If the type implemented the interface `error`, fmt will call `error()`, so you don't have to implement Stringer at this point.
### Type of variable in an interface
Achtung: Wenn Du das Interface `error` implementierst, wird `fmt` `error()` ausrufen, sodass Du ab hier Stringer noch nicht definieren brauchst.
If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you.
### Datentyp eines Interfaces bestimmen
- Assertion of Comma-ok pattern
Wir wissen, dass einer Variable jeder Datentyp zugewiesen werden kann, der ein Interface mit dem originalen Datentypen teilt. Nun stellt sich die Frage, wie wir den genauen Datentypen einer Variable bestimmen können. Hierfür gibt es zwei Wege, die ich Dir zeigen möchte.
Go has the syntax `value, ok := element.(T)`. This checks to see if the variable is the type that we expect, where "value" is the value of the variable, "ok" is a variable of boolean type, "element" is the interface variable and the T is the type of assertion.
- Überprüfung nach dem Komma-ok-Muster
If the element is the type that we expect, ok will be true, false otherwise.
Der in Go übliche Syntax lautet `value, ok := element.(T)`. Er überprüft, ob eine Variable vom erwarteten Datentypen ist. "value" ist der Wert der Variable,"ok" ist vom Typ Boolean, "element" ist eine Interfacevariable und T der zu überprüfende Datentyp.
Let's use an example to see more clearly.
Wenn das Element dem erwarteten Datentypen entspricht, wird ok auf true gesetzt. Anderfalls ist er false.
Veranschaulichen wir dies anhand eines Beispiels.
package main
@@ -246,41 +247,41 @@ Let's use an example to see more clearly.
)
type Element interface{}
type List []Element
type Liste []Element
type Person struct {
name string
age int
name string
alter int
}
func (p Person) String() string {
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
return "(Name: " + p.name + " - Alter: " + strconv.Itoa(p.alter) + " Jahre)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
liste := make(Liste, 3)
liste[0] = 1 // ein Integer
liste[1] = "Hallo" // ein String
liste[2] = Person{"Dennis", 70}
for index, element := range list {
for index, element := range liste {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
fmt.Printf("liste[%d] ist ein Integer mit dem Wert %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
fmt.Printf("liste[%d] ist ein String mit dem Wert %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
fmt.Printf("liste[%d] ist eine Person mit dem Wert %s\n", index, value)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
fmt.Printf("liste[%d] hat einen anderen Datentyp\n", index)
}
}
}
It's quite easy to use this pattern, but if we have many types to test, we'd better use `switch`.
Dieses Muster ist sehr einfach anzuwenden, aber wenn wir viele Datentypen zu bestimmen haben, sollten wir besser `switch` benutzen.
- switch test
- Überprüfung mit switch
Let's use `switch` to rewrite the above example.
Machen wir von `switch` gebrauch und schreiben unser Beispiel um.
package main
@@ -290,65 +291,64 @@ Let's use `switch` to rewrite the above example.
)
type Element interface{}
type List []Element
type Liste []Element
type Person struct {
name string
age int
name string
alter int
}
func (p Person) String() string {
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
return "(Name: " + p.name + " - Alter: " + strconv.Itoa(p.alter) + " Jahre)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
liste := make(Liste, 3)
liste[0] = 1 // Ein Integer
liste[1] = "Hello" // Ein String
liste[2] = Person{"Dennis", 70}
for index, element := range list {
for index, element := range liste {
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
fmt.Printf("liste[%d] ein Integer mit dem Wert %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
fmt.Printf("liste[%d] ist ein String mit dem Wert %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
fmt.Printf("liste[%d] ist eine Person mit dem Wert %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
fmt.Println("liste[%d] hat einen anderen Datentyp", index)
}
}
}
One thing you should remember is that `element.(type)` cannot be used outside of the `switch` body, which means in that case you have to use the `comma-ok` pattern .
### Embedded interfaces
Eine Sache, die Du bedenken solltest, ist, dass `element.(type)` nur in Kombination mit `switch` genutzt werden kann. Andernfalls musst Du auf das Komma-ok-Muster zurückgreifen.
The most beautiful thing is that Go has a lot of built-in logic syntax, such as anonymous fields in struct. Not suprisingly, we can use interfaces as anonymous fields as well, but we call them `Embedded interfaces`. Here, we follow the same rules as anonymous fields. More specifically, if an interface has another interface embedded within it, it will have as if it has all the methods that the embedded interface has.
### Eingebettete Interfaces
We can see that the source file in `container/heap` has the following definition:
Eine der schönsten Eigenschaften von Go ist dessen eingebaute und vorrausschauende Syntax, etwa namenlose Eigentschaften in Structs. Nicht überraschend können wir dies ebenfalls mit Interfaces tun, die `eingebettete Interfaces` genannt werden. Auch hier gelten die selben Regeln wie bei namenlosen Eigenschaften. Anders ausgedrückt: wenn ein Interface ein anderes Interface einbettet, werden auch alle Methoden mit übernommen.
Im Quellcode des Pakets `container/heap` lässt sich folgende Definition finden:
type Interface interface {
sort.Interface // embedded sort.Interface
Push(x interface{}) //a Push method to push elements into the heap
Pop() interface{} //a Pop elements that pops elements from the heap
sort.Interface // Eingetettetes sort.Interface
Push(x interface{}) // Eine Push Methode, um Objekte im Heap zu speichern
Pop() interface{} // Eine Pop Methode, um Elemente aus dem heap zu löschen
}
We see that `sort.Interface` is an embedded interface, so the above Interface has the three methods contained within the `sort.Interface` implicitly.
Wie wir sehen können, handelt es sich bei `sort.Interface` um ein eingebettes Interface. Es beinhaltet neben `Push()` und `Pop()` die folgenden drei Methoden implizit.
type Interface interface {
// Len is the number of elements in the collection.
// Len gibt die Anzahl der Objekte in der Datenstruktur an.
Len() int
// Less returns whether the element with index i should sort
// before the element with index j.
// Less gibt in Form eines Boolean an, ob i mit j getauscht werden sollte
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
// Swap vertauscht die Elemente i und j.
Swap(i, j int)
}
Another example is the `io.ReadWriter` in package `io`.
Ein weiteres Beispiel ist `io.ReadWriter` aus dem Paket `io`.
// io.ReadWriter
type ReadWriter interface {
@@ -356,40 +356,42 @@ Another example is the `io.ReadWriter` in package `io`.
Writer
}
### Reflection
### Reflexion
Reflection in Go is used for determining information at runtime. We use the `reflect` package, and this official [article](http://golang.org/doc/articles/laws_of_reflection.html) explains how reflect works in Go.
Reflexion in Go wird genutzt, um Informationen während der Laufzeit zu bestimmen. Wir nutzen dafür das `reflect` Paket. Der offizelle [Artikel](http://golang.org/doc/articles/laws_of_reflection.html) erklärt die Funktionsweise von `reflect` in Go.
There are three steps involved when using reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation).
Die Nutzung von `reflect` umfasst drei Schritte. Als Erstes müssen wir ein Interface in `reflect`-Datentypen umwandeln (entweder in reflect.Type oder reflect.Value, aber dies ist Situationsabhängig).
t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements
v := reflect.ValueOf(i) // get actual value in type i, and use v to change its value
t := reflect.TypeOf(i) // Speichert den Datentyp von i in t und erlaubt den Zugriff auf alle Elemente
v := reflect.ValueOf(i) // Erhalte den aktuellen Wert von i. Nutze v um den Wert zu ändern
After that, we can convert the reflected types to get the values that we need.
Danach können wir die reflektierten Datentypen konvertieren, um ihre Werte zu erhalten.
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
fmt.Println("Datentyp:", v.Type())
fmt.Println("Die Variante ist float64:", v.Kind() == reflect.Float64)
fmt.Println("Wert:", v.Float())
Finally, if we want to change the values of the reflected types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile.
Wollen wir schließlich den Wert eines reflektierten Datentypen ändern, müssen wir ihn dynamisch machen. Wie vorhin angesprochen, gibt es einen Unterschied, wenn wir einen Wert als Kopie oder dessen Zeiger übergeben. Das untere Beispiel ist nicht kompilierbar.
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
Instead, we must use the following code to change the values from reflect types.
Stattdessen müssen wir den folgenden Code verwenden, um die Werte der reflektierten Datentypen zu ändern.
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
We have just discussed the basics of reflection, however you must practice more in order to understand more.
Nun kennen wir die Grundlagen der Reflexion. Es erfordert jedoch noch ein wenig Übung, um sich mit diesem Konzept vertraut zu machen.
## Links
- [Directory](preface.md)
- Previous section: [Object-oriented](02.5.md)
- Next section: [Concurrency](02.7.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Objektorientierte Programmierung](02.5.md)
- Nächster Abschnitt: [Nebenläufigkeit](02.7.md)

View File

@@ -1,242 +1,244 @@
# Concurrency
# 2.7 Nebenläufigkeit
It is said that Go is the C language of the 21st century. I think there are two reasons: first, Go is a simple language; second, concurrency is a hot topic in today's world, and Go supports this feature at the language level.
Es wird behauptet, Go sei das C des 21. Jahrhunderts. Ich glaube, dafür gibt es zwei Gründe: erstens, Go ist eine simple Programmiersprache; zweitens: Nebenläufigkeit (Concurrency im Englischen) ist ein heißes Thema in der heutigen Welt und Go unterstützt die Eigenschaft als ein zentraler Aspekt der Sprache.
## goroutine
## Goroutinen
goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. More than a dozen goroutines maybe only have 5 or 6 underlying threads. Go also gives you full support to sharing memory in your goroutines. One goroutine usually uses 4~5 KB of stack memory. Therefore, it's not hard to run thousands of goroutines on a single computer. A goroutine is more lightweight, more efficient and more convenient than system threads.
Goroutinen und Nebenläufigkeit sind zwei wichtige Komponenten im Design von Go. Sie ähneln Threads, funktionieren aber auf eine andere Weise. Ein dutzend Goroutinen haben vielleicht nur fünf oder sechs zugrundeliegende Threads. Des Weiteren unterstützt Go vollständig das Teilen von Speicherressourcen zwischen den Goroutinen. Eine Goroutine nimmt gewöhnlicherweise etwa 4 bis 5 KB Speicher ein. Daher ist es nicht schwer, tausende von Goroutinen auf einem einzelnen Computer zu nutzen. Goroutinen sind weniger ressourcenhungrig, effizienter und geeigneter als Systemthreads.
goroutines run on the thread manager at runtime in Go. We use the `go` keyword to create a new goroutine, which is a function at the underlying level ( ***main() is a goroutine*** ).
Goroutinen laufen im Thread Manager während der Laufzeit von Go. Wir nutzen das Schlüsselwort `go`, um eine neue Goroutine zu erstellen, wobei es sich eigentlich um eine interne Funktion von Go handelt ( ***main() ist ebenfalls eine Goroutine*** ).
go hello(a, b, c)
Let's see an example.
go Hallo(a, b, c)
Schauen wir uns ein Beispiel an.
package main
package main
import (
"fmt"
"runtime"
)
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func sag(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world") // create a new goroutine
say("hello") // current goroutine
}
func main() {
go sag("Welt") // Erzeugt eine neue Goroutine
sag("Hallo") // Aktuelle Goroutine
}
Output
Ausgabe:
hello
world
hello
world
hello
world
hello
world
hello
We see that it's very easy to use concurrency in Go by using the keyword `go`. In the above example, these two goroutines share some memory, but we would better off following the design recipe: Don't use shared data to communicate, use communication to share data.
Hallo
Welt
Hallo
Welt
Hallo
Welt
Hallo
Welt
Hallo
Wie es scheint, ist es sehr einfach, Nebenläufigkeit in Go durch das Schlüsselwort `go` zu nutzen. Im oberen Beispiel teilen sich beide Goroutinen den selben Speicher. Aber es wäre besser, diesem Rat folge zu leisten: Nutze keine geteilten Daten zur Kommunikation, sondern kommuniziere die geteilten Daten.
runtime.Gosched() means let the CPU execute other goroutines, and come back at some point.
`runtime.Gosched()` bedeutet, das die CPU andere Goroutinen ausführen und nach einiger Zeit an den Ausgangspunkt zurückkehren soll.
The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. This function may be removed in the future, see more details about parallel processing and concurrency in this [article](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
Das Steuerungsprogramm nutzt einen Thread, um alle Goroutinen auszuführen. Das bedeutet, dass einzig dort Nebenläufigkeit implementiert wird. Möchtest Du mehr Rechnenkerne im Prozessor nutzen, um die Vorteile paralleler Berechnungen einzubringen, musst Du `runtime.GOMAXPROCS(n)` aufrufen, um die Anzahl der Rechenkerne festzulegen. Gilt `n<1`, verändert sich nichts. Es könnte sein, dass diese Funktion in Zukunft entfernt wird. Für weitere Informationen zum verteilten Rechnen und Nebenläufigkeit findest Du in diesem [Artikel](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
## channels
## Channels
goroutines run in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. `channel` is like a two-way pipeline in Unix shells: use `channel` to send or receive data. The only data type that can be used in channels is the type `channel` and the keyword `chan`. Be aware that you have to use `make` to create a new `channel`.
Goroutinen werden im selben Adressraum des Arbeitsspeichers ausgeführt, sodass Du in den Goroutinen die genutzen Ressourcen synchronisieren musst, wenn diese geteilt werden sollen. Aber wie kommuniziere ich zwischen verschiedenen Goroutinen? Hierfür nutzt Go einen sehr guten Mechanismus mit dem Namen `channel`. `channel` ist wie eine bidirektionale Übertragungsleitung (Pipe) in Unix-Shells: nutze `channel` um Daten zu senden und zu empfangen. Der einzige Datentyp, der in Kombination mit diesen Datenkanälen genutzt werden kann, ist der Typ `channel` und das Schlüsselwort `chan`. Beachte, dass Du `make` brauchst, um einen neuen `channel` zu erstellen.
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
channel uses the operator `<-` to send or receive data.
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
`channel` nutzt den Operator `<-`, um Daten zu senden und zu empfangen.
ch <- v // send v to channel ch.
v := <-ch // receive data from ch, and assign to v
Let's see more examples.
ch <- v // Sende v an den Kanal ch.
v := <-ch // Empfange Daten von ch und weise sie v zu
Schauen wir uns weitere Beispiele an.
package main
package main
import "fmt"
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total // send total to c
}
func summe(a []int, c chan int) {
gesamt := 0
for _, v := range a {
gesamt += v
}
c <- gesamt // Sende gesamt an c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
c := make(chan int)
go summe(a[:len(a)/2], c)
go summe(a[len(a)/2:], c)
x, y := <-c, <-c // Empfange Daten von c
fmt.Println(x, y, x + y)
}
Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that a goroutine will not continue when receiving data from an empty channel, i.e (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue until the data it sends to a channel, i.e (`ch<-5`), is received.
fmt.Println(x, y, x + y)
}
## Buffered channels
Das Senden und Empfangen von Daten durch die Datenkanäle wird standardmäßig gestoppt, um die Goroutinen einfach synchron zu halten. Mit dem Blocken meine ich, dass eine Goroutine nicht weiter ausgeführt wird, sobald keine Daten mehr von einem `channel` empfangen werden (z.B. `value := <-ch`) und andere Goroutinen keine weiteren Daten über den entsprechenden Kanal senden. Anderseits stoppt die sendende Goroutine solange, bis alle Daten (z.B. `ch<-5`)über den Kanal empfangen wurden.
I introduced non-buffered channels above. Go also has buffered channels that can store more than a single element. For example, `ch := make(chan bool, 4)`, here we create a channel that can store 4 boolean elements. So in this channel, we are able to send 4 elements into it without blocking, but the goroutine will be blocked when you try to send a fifth element and no goroutine receives it.
## Gepufferte Channels
ch := make(chan type, n)
Eben habe ich die nicht-gepuffter Datenkanäle vorgestellt. Go unterstützt aber auch gepufferte Channel, die mehr als ein Element speichern können, z.B. `ch := make(chan bool, 4)`. Hier wurde ein Channel mit der Kapazität von vier Booleans erstellt. Mit diesem Datenkanal sind wir in der Lage, vier Elemente zu senden, ohne das die Goroutine stoppt. Dies passiert aber bei dem Versuch, ein fünftes Element zu versenden, ohne das es von einer Goroutine empfangen wird.
n == 0 ! non-bufferblock
n > 0 ! buffernon-block until n elements in the channel
You can try the following code on your computer and change some values.
package main
ch := make(chan type, n)
import "fmt"
n == 0 ! nicht-gepuffertstoppt
n > 0 ! gepuffertnicht gestoppt, sobald n Elemente im Kanal sind
Experimentiere mit dem folgenden Code auf Deinem Computer und verändere die Werte.
package main
func main() {
c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
## Range and Close
import "fmt"
We can use range to operate on buffer channels as in slice and map.
func main() {
c := make(chan int, 2) // Setze 2 auf 1 und Du erzeugst einen Laufzeitfehler. Aber 3 ist OK.
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
## Range und Close
package main
Wir können `range` in gepufferten Kanlen genauso nutzen, wie mit Slices und Maps.
import (
"fmt"
)
package main
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
import (
"fmt"
)
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
`for i := range c` will not stop reading data from channel until the channel is closed. We use the keyword `close` to close the channel in above example. It's impossible to send or receive data on a closed channel; you can use `v, ok := <-ch` to test if a channel is closed. If `ok` returns false, it means the there is no data in that channel and it was closed.
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
Remember to always close channels in producers and not in consumers, or it's very easy to get into panic status.
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
`for i := range c` wird nicht eher mit dem Lesen von Daten aus dem Channel aufhören, ehe dieser geschlossen ist. Wir nutzen das Schlüsselwort `close`, um den Datenkanal im oberen Beispiel zu schließen. Es ist unmöglich, Daten über einen geschlossenen Channel zu senden oder zu empfangen. Mit `v, ok := <-ch` kannst Du den Status eines Kanals überprüfen. Wird `ok` auf false gesetzt, bedeutet dies, dass sich keine weiteren Daten im Channel befinden und er geschlossen wurde.
Another thing you need to remember is that channels are not like files. You don't have to close them frequently unless you are sure the channel is completely useless, or you want to exit range loops.
Denke aber immer daran, die Datenkanäle auf seiten der Datenproduzenten zu schließen und nicht bei den Empfängern der Daten. Andernfalls kann es passieren, dass sich Dein Programm in den Panikmodus versetzt.
Ein weiterer Aspekt, den wir nicht unterschlagen sollten, ist, dass Du Channels nicht wie Dateien behandeln solltest. Du brauchst sie nicht andauernd schließen, sondern erst, wenn Du sicher bist, dass sie nicht mehr gebraucht werden oder Du das Abfragen der übertragenen Daten mit dem Schlüsselwort `range` beenden willst.
## Select
In the above examples, we only use one channel, but how can we deal with more than one channel? Go has a keyword called `select` to listen to many channels.
In den vorherigen Beispielen haben wir bisher immer nur einen Datenkanal verwendet, aber wie können wir Gebrauch von mehreren Channels machen? Go erlaubt es, mit dem Schlüsselwort `select` viele Kanäle nach Daten zu belauschen.
`select` is blocking by default and it continues to execute only when one of channels has data to send or receive. If several channels are ready to use at the same time, select chooses which to execute randomly.
`select` stoppt standardmäßig eine Goroutine und wird einzig ausgeführt, wenn einer der Channels Daten sendet oder empfängt. Sollten mehrere Kanäle zur gleichen Zeit aktiv sein, wird ein zufälliger ausgewählt.
package main
package main
import "fmt"
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("Fertig")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
`select` has a `default` case as well, just like `switch`. When all the channels are not ready for use, it executes the default case (it doesn't wait for the channel anymore).
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
select {
case i := <-c:
// use i
default:
// executes here when c is blocked
}
## Timeout
`select` hat ebenfalls einen Standardfall wie `switch`, mit dem Namen `default`. Wenn kein Datenkanal aktiv sein sollte, wird der Standardfall ausgeführt (es wird auf keinen Kanal mehr gewartet).
Sometimes a goroutine becomes blocked. How can we avoid this to prevent the whole program from blocking? It's simple, we can set a timeout in the select.
select {
case i := <-c:
// Benutze i
default:
// Dieser Code wird ausgeführt, sollte c gestoppt worden sein
}
## Zeitüberschreitung
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("timeout")
o <- true
break
}
}
}()
<- o
}
Manchmal kann es vorkommen, dass eine Goroutine gestoppt wird. Wie können wir verhindern, dass daraus resultierend das gesamte Programm aufhört zu arbeiten? Es ist ganz einfach. Es muss lediglich eine Zeitüberschreitung in `select` festgelegt werden.
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("Zeitüberschreitung")
o <- true
break
}
}
}()
<- o
}
## Runtime goroutine
The package `runtime` has some functions for dealing with goroutines.
Das Paket `runtime` beinhaltet ein paar Funktionen zum Umgang mit Goroutinen.
- `runtime.Goexit()`
Exits the current goroutine, but defered functions will be executed as usual.
Verlässt die aktuelle Goroutine, aber verzögerte Funktionen werden wie gewohnt ausgeführt.
- `runtime.Gosched()`
Lets the scheduler execute other goroutines and comes back at some point.
Lässt die CPU vorerst andere Goroutinen ausführen und kehrt nach einiger Zeit zum Ausgangspunkt zurück.
- `runtime.NumCPU() int`
Returns the number of CPU cores
Gibt die Anzahl der Rechenkerne zurück.
- `runtime.NumGoroutine() int`
Returns the number of goroutines
Gibt die Anzahl der Goroutinen zurück.
- `runtime.GOMAXPROCS(n int) int`
Sets how many CPU cores you want to use
Legt die Anzahl der Rechenkerne fest, die benutzt werden sollen.
## Links
- [Directory](preface.md)
- Previous section: [interface](02.6.md)
- Next section: [Summary](02.8.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Interfaces](02.6.md)
- Nächster Abschnitt: [Zusammenfassung](02.8.md)

View File

@@ -1,32 +1,32 @@
# 2.8 Summary
# 2.8 Zusammenfassung
In this chapter, we mainly introduced the 25 Go keywords. Let's review what they are and what they do.
In diesem Kapitel haben wir uns hauptsächlich mit den 25 Schlüsselwörtern in Go auseinandergesetzt. Schauen wir sie und ihre Funktionweise uns noch einmal an.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
- `var` and `const` are used to define variables and constants.
- `package` and `import` are for package use.
- `func` is used to define functions and methods.
- `return` is used to return values in functions or methods.
- `defer` is used to define defer functions.
- `go` is used to start a new goroutine.
- `select` is used to switch over multiple channels for communication.
- `interface` is used to define interfaces.
- `struct` is used to define special customized types.
- `break`, `case`, `continue`, `for`, `fallthrough`, `else`, `if`, `switch`, `goto` and `default` were introduced in section 2.3.
- `chan` is the type of channel for communication among goroutines.
- `type` is used to define customized types.
- `map` is used to define map which is similar to hash tables in other languages.
- `range` is used for reading data from `slice`, `map` and `channel`.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
- `var` und `const` werden benutzt, um Variablen und Konstanten zu definieren.
- `package` und `import` sind für die Nutzung von Paketen nötig.
- `func` wird zur Definition von Funktionen und Methoden verwendet.
- `return` wird genutzt, um Werte von Funktionen und Methoden zurückzugeben.
- `defer` wird genutzt, um Funktionen zu definieren, die als letzte Anweisung ausgeführt werden.
- `go` definiert und startet eine neue Goroutine.
- `select` erlaubt das Kommunizieren über mehrere Channels.
- `interface` definiert ein Interface.
- `struct` lässt uns speziell angepasste Datentypen erstellen.
- `break`, `case`, `continue`, `for`, `fallthrough`, `else`, `if`, `switch`, `goto` und `default` wurden in Abschnitt 2.3 vorgestellt.
- `chan` ist ein Datentypen für Channel, die es erlauben, zwischen Goroutinen zu kommunizieren.
- `type` dient zur Erstellung eigener Datentypen.
- `map` definiert eine Map, welche Hashtabellen in anderen Programmiersprachen ähneln.
- `range` wird genutzt, um Daten aus einem `slice`, einer `map` oder einem`channel` zu erhalten.
If you understand how to use these 25 keywords, you've learned a lot of Go already.
Wenn Du verstanden was, wie die 25 Schlüsselwörter einzusetzen sind, dann hast Du bereits eine Menge über Go gelernt.
## Links
- [Directory](preface.md)
- Previous section: [Concurrency](02.7.md)
- Next chapter: [Web foundation](03.0.md)
- [Inhaltsverzeichnis](preface.md)
- Vorheriger Abschnitt: [Nebenläufigkeit](02.7.md)
- Nächstes Kapitel: [Grundlagen des Internets](03.0.md)