Add German translation of chapter 2
This commit is contained in:
805
de/02.3.md
805
de/02.3.md
@@ -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)
|
||||
}
|
||||
// Print:sum 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.
|
||||
|
||||

|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user