Kontrollstrukturen geben Go die nötige Klarheit und Vorhersehbarkeit. if-Verzweigungen decken einfache Entscheidungen ab, switch fasst Zustände elegant zusammen, und for bildet das flexible Rückgrat für Iterationen. select steuert nebenläufige Abläufe präzise, indem es Kommunikationswege zwischen Goroutines koordiniert. Zusammen ermöglichen diese Bausteine robuste, gut lesbare und wartbare Anwendungen.

Einführung

Kontrollstrukturen sind die Bausteine, die den Ablauf eines Programms steuern. Sie bestimmen, welche Code-Abschnitte ausgeführt werden, wie oft sie wiederholt werden und unter welchen Bedingungen bestimmte Aktionen stattfinden. In Go gibt es eine überschaubare, aber mächtige Sammlung von Kontrollstrukturen, die sich durch ihre Einfachheit und Klarheit auszeichnet.

Im Gegensatz zu vielen anderen Programmiersprachen hat Go bewusst auf einige Kontrollstrukturen verzichtet, um die Sprache schlank und vorhersehbar zu halten.

Das bedeutet unter Anderem:

  • Keine while Schleife: Go hat nur for, das aber flexibel genug ist, um alle Schleifen-Anforderungen abzudecken.
  • Keine do-while Schleife: Auch dies wird durch for abgedeckt.
  • Keine ternären Operatoren (? :): Stattdessen wird if-else verwendet.
  • Kein automatisches Fallthrough in switch: Im Gegensatz zu C/C++/Java ist break nicht nötig.

Diese bewussten Einschränkungen machen Go-Code lesbarer und weniger fehleranfällig.

if / else - Bedinge Ausführung

Grundlegende Syntax

Die if Anweisung führt einen Code-Block nur aus, wenn eine Bedingung true (also erfüllt) ist.

Syntax

Go
if bedingung {
    // Code wird ausgeführt, wenn true
}

Wichtige Punkte

  • Keine Klammern um die Bedingung: In Go sind () um die Bedingung optional und werden idiomatisch weggelassen.
  • Geschweifte Klammern sind Pflicht: {} müssen immer verwendet werden, auch bei einzeiligem Code
  • Öffnende Klammer auf der gleichen Zeile: { muss auf derselben Zeile wie if stehen (Go-Syntax-Regel)
Go Beispiel
package main

import "fmt"

func main() {
    age := 18

    if age >= 18 {
        fmt.Println("Du bist volljährig")
    }
}

if-else Verzweigung

Mit else kann ein alternativer Code-Block angegeben werden, der ausgeführt wird, wenn die Bedingung false ist.

Syntax

Go
if bedingung {
    // Code wenn true
} else {
    // Code wenn false
}

Beispiel

Go
package main

import "fmt"

func main() {
    num := 100

    if num >= 200 {
        fmt.Println("Nummer ist größer 200")
    } else {
        fmt.Println("Nummer ist kleiner 200")
    }
}

else if - Mehrfachverzweigung

Mit else if können mehrere Bedingungen nacheinander geprüft werden.

Syntax

Go
if bedingung1 {
    // Code wenn bedingung1 true
} else if bedingung2 {
    // Code wenn bedingung2 false
} else {
    // Code wenn keine Bedingung true
}

Beispiel

Go
package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Note: Sehr gut")
    } else if score >= 80 {
        fmt.Println("Note: Gut")
    } else if score >= 70 {
        fmt.Println("Note: Befriedigend")
    } else if score >= 60 {
        fmt.Println("Note: Ausreichend")
    } else {
        fmt.Println("Note: Nicht bestanden")
    }
}

if mit Initialisierungs-Statement

Go erlaubt eine besondere Syntax: Man kann eine Variable direkt in der if Anweisung initialisieren. Diese Variable ist dann nur im Scope des if Blocks (und else Blocks) verfügbar.

Syntax

Go
if initialisierung; bedingung {
    // Code
}

Beispiel

Go
package main

import "fmt"

func main() {
    if age := 20; age >= 18 {
        fmt.Println("Volljährig, Alter:", age)
    }
}

Ohne dieser Kurz-Initialisierung könnte man die gleiche Logik auch wie folgt schreiben.

Go
package main

import "fmt"

func main() {
    age := 20
    if age >= 18 {
        fmt.Println("Volljährig, Alter:", age)
    }
}

Wichtige Punkte:

  • Die Variable age existiert nur innerhalb des if Blocks (und des zugehörigen else)
  • Nach dem if ist age nicht mehr zugänglich

Schauen wir uns ein praktisches Beispiel mit Fehlerbehandlung. Dieses Pattern wird in Go sehr oft angewendet.

Go Beispiel
package main

import (
    "fmt"
    "strconv"
)

func main() {
    input := "123"

    if num, err := strconv.Atoi(input); err == nil {
        fmt.Println("Erfolgreich konvertiert:", num)
        fmt.Println("Doppelt:", num*2)
    } else {
        fmt.Println("Fehler:", err)
    }
}
Output
Erfolgreich konvertiert: 123
Doppelt: 246

Was passiert in diesem Beispiel?

  1. strconv.Atoi(input) wird aufgerufen und gibt zwei Werte zurück: num und err
  2. Die Bedingung err == nil prüft, ob kein Fehler aufgetreten ist
  3. Wenn err == nil (kein Fehler), wird der if Block ausgeführt
  4. Die Variablen num und err sind nur im if und else Block verfügbar

Verschachtelte if-Anweisungen

if Anweisungen können beliebig verschachtelt werden.

Go Beispiel
package main

import "fmt"

func main() {
    age := 20
    hasLicense := true

    if age >= 18 {
        if hasLicense {
            fmt.Println("Du darfst Auto fahren")
        } else {
            fmt.Println("Du musst noch einen Führerschein machen")
        }
    } else {
        fmt.Println("Du bist zu jung zum Autofahren")
    }
}
Output
Du darfst Auto fahren.

Diese Logik können wir auch mit logischen Operatoren schreiben. Verschachtelte if Anweisungen können oft durch logische Operatoren (&& , ||) vereinfacht werden.

Go Beispiel
package main

import "fmt"

func main() {
    age := 20
    hasLicense := true

    if age >= 18 && hasLicense {
        fmt.Println("Du darfst Auto fahren")
    } else if age >= 18 {
        fmt.Println("Du musst noch den Führerschein machen")
    } else {
        fmt.Println("Du bist zu jung zum Autofahren")
    }
}

Early Returns (Guard Clauses)

Ein wichtiges Pattern in Go ist das Early Return oder Guard Clause Pattern. Anstatt tief verschachtelte if-else Strukturen zu verwenden, prüft man zuerst auf Fehlerbedingungen und kehrt früh zurück.

Im Folgenden schauen wir uns zwei Beispiele an. Das erste Beispiel ist funktionsfähig, aber weniger gut von der Struktur. Im zweiten Beispiel schauen wir uns, wie man es eleganter lösen kann.

Schlechtes Beispiel (viele Verschachtelung)

Go Beispiel - Schlecht
func processUser(user *User) error {
    if user != nil {
        if user.IsActive {
            if user.HasPermission("write") {
                // Eigentliche Logik
                return doSomething(user)
            } else {
                return errors.New("keine Berechtigung")
            }
        } else {
            return errors.New("Benutzer ist inaktiv")
        }
    } else {
        return errors.New("Benutzer ist nil")
    }
}

Im Vergleich dazu nun das Beispiel mit Early Returns.

Go Beispiel - Gut
func processUser(user *User) error {
    if user == nil {
        return errors.New("Benutzer ist nil")
    }

    if !user.IsActive {
        return errors.New("Benutzer ist inaktiv")
    }

    if !user.HasPermission("write") {
        return errors.New("keine Berechtigung")
    }

    // Eigentliche Logik

    return doSomething(user)
}

Vorteile:

  • Weniger Verschachtelung: Der Code bleibt flach und ist leichter zu lesen
  • Klare Fehlerbehandlung: Fehlerfälle werden zuerst abgearbeitet
  • Klare Reihenfolge: Die Hauptlogik steht am Ende ohne Verschachtelung

Bedingungen und Wahrheitswerte

Was ist eine gültige Bedingung?

In Go muss eine Bedingung immer vom Typ bool sein. Im Gegensatz zu Sprachen wie C oder JavaScript gibt es keine implizite Konvertierung zu Boolean.

Folgender Code würde in Go nicht funktionieren.

Go
x := 5
if x {
    fmt.Println("x ist wahr")
}

Dagegen würde dieser Code korrekt funktionieren.

Go
x := 5
if x != 0 {
    fmt.Println("x ist wahr")
}

Vergleichsoperatoren

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

Logische Operatoren

  • && - logisches UND (beide Seiten müssen true sein)
  • || - logisches ODER (mindestens eine Seite muss true sein)
  • ! - logisches NICHT (negiert den Wahrheitswert)

Beispiel

Go
package main

import "fmt"

func main() {
    age := 25
    income := 30000

    // UND: Beide Bedinungen müssen true sein
    if age >= 18 && income >= 25000 {
        fmt.Println("Kreditantrag genehmigt")
    }

    // ODER: Mindestens eine Bedingung muss true sein
    if age < 18 || income < 25000 {
        fmt.Println("Weitere Prüfung erforderlich")
    }

    // NICHT: Negation
    isMinor := age < 18
    if !isMinor {
        fmt.Println("Volljährig")
    }
}

Short-Circuit Evaluation

Go verwendet Short-Circuit Evaluation bei && und ||.

  • Bei &&: Wenn die linke Seite false ist, wird die rechte Seite nicht ausgewertet
  • Bei ||: Wenn die linke Seite true ist, wird die rechte Seite nicht ausgewertet
Go Beispiel
package main

import "fmt"

func isPositive(n int) bool {
    fmt.Println("isPositive aufgerufen mit", n)
    return n > 0
}

func main() {
    // Linke Seite ist false
    // Rechte Seite wird nicht ausgewertet
    if false && isPositive(5) {
        fmt.Println("Dieser Code wird nie ausgeführt")
    }

    // Linke Seite ist true
    // Rechte Seite wird nicht ausgewertet
    if true || isPositive(5) {
        fmt.Println("True")
    }
}

switch - Mehrfachverzweigung

Die switch Anweisung ist eine elegante Alternative zu langen if-else Ketten. Go’s switch ist mächtiger und flexibler als in vielen anderen Sprachen.

Expression Switch

Ein Expression Switch vergleicht einen Ausdruck mit verschiedenen Werten.

Syntax

Go
switch ausdruck {
case wert1:
    // Code wenn ausdruck == wert1

case wert2:
    // Code wenn ausdruck == wert2

default:
    // Code wenn keine case-Bedingung
}
Go Beispiel
package main

import "fmt"

func main() {
    day := "Montag"

    switch day {
    case "Montag":
        fmt.Println("Start der Woche")

    case "Freitag":
        fmt.Println("Fast Wochenende")

    case "Samstag", "Sonntag":
        fmt.Println("Wochenende!")
    
    default:
        fmt.Println("Ein normaler Tag")
    }
}

Was passiert hier

  1. Der Wert von day wird ausgewertet (“Montag”)

  2. Go vergleicht day mit jedem case von oben nach unten

  3. Der erste zutreffende case wird ausgeführt

  4. Kein automatisches Fallthrough - nach dem case wird der switch automatisch beendet (kein break nötig)

    Auch, wenn kein break notwendig ist, kann es dennoch verwendet werden, um einen case Block vorzeitig zu verlassen.

switch mit Initialisierungs-Statement

Wie bei if kann auch switch eine Initialisierung haben.

Syntax

Go
switch initialisierung; ausdruck {
case wert1:
    // Code wenn ausdruck == wert1

default:
    // Code wenn keine case-Bedingung
}

Zudem kann es auch ein switch ohne Verwendung von Ausdruck geben. Dabei wird der Wert aus der Initialisierung auf cases überprüft.

Das ; Semikolon trennt die Initialisierung vom Ausdruck.

Im nachfolgenden Beispiel werden wir nur die Variable aus der Initialisierung verwenden.

Go Beispiel
package main

import (
    "fmt"
    "time"
)

func main() {
    switch hour := time.Now().Hour(); {
    case hour < 12:
        fmt.Println("Guten Morgen")
    
    case hour < 18:
        fmt.Println("Guten Tag")

    default:
        fmt.Println("Guten Abend")
    }
}
Output
Guten Morgen

Was genau passiert hier?

Wir definieren eine Variable hour := time.Now().Hour(). Das Init-Statement wird ausgeführt. Nach dem Semikolon folgt kein expliziter Switch-Ausdruck. In Go bedeutet ein fehlender Switch-Ausdruck implizit true. Die case Statements werden als boolesche Ausdrücke ausgewertet.

Das obere Konstrukt ist äquivalent zu folgendem Code:

Go
switch hour := time.Now().Hour(); true {

}

switch ohne Bedingung

Eine besonders mächtige Variant ist switch ohne Ausdruck. Dies ist eine elegante Alternative zu if-else Ketten.

Syntax

Go
switch {
case bedingung1:
    // Code
case bedingung2:
    // Code
default:
    // Code
}

Jeder case enthält seine eigene Bedingung (anstatt einen Vergleichswert).

Go Beispiel
package main

import "fmt"

func main() {
    score := 85

    switch {
    case score >= 90:
        fmt.Println("Sehr gut")
    case score >= 80:
        fmt.Println("Gut")
    case score >= 70:
        fmt.Println("Befriedigend")
    case score >= 60:
        fmt.Println("Ausreichend")
    default:
        fmt.Println("Nicht bestanden")
    }
}

Warum kann es nützlich sein?

  • Übersichtlicher als lange if-else Ketten
  • Jeder case kann eine unterschiedliche Bedingung haben
  • Erster zutreffender case wird ausgeführt

Fallthrough - Explizites Durchfallen

Wenn man das Fallthrough-Verhalten von C/Java benötigt, kann man das Schlüsselwort fallthrough verwenden.

Go Beispiel
package main

import "fmt"

func main() {
    num := 1 

    switch num {
    case 1:
        fmt.Println("Eins")
        fallthrough // Fällt in den nächsten Case
    case 2:
        fmt.Println("Zwei")
        fallthrough
    case 3:
        fmt.Println("Drei")
    default:
        fmt.Println("Standard")
    }
}
Output
Eins
Zwei
Drei

Was ist hier passiert?

  1. case 1 trifft zu => “Eins” wird ausgegeben
  2. fallthrough sorgt dafür, dass case 2 ebenfalls ausgeführt wird (ohne Bedingungsprüfung)
  3. Wieder fallthrough => case 3 wird ausgeführt
  4. Kein fallthrough mehr => switch endet

Wichtig zu merken:

  • fallthrough führt den nächsten case bedingungslos aus (ohne die case Bedingung zu prüfen)
  • fallthrough kann nicht im letzten case oder default verwendet werden
  • fallthrough wird in der Praxis selten verwendet

Type Switch - Interface-Typ-Prüfung

Ein Type Switch prüft den dynamischen Typ eines Interface-Werts. Dies ist besonders dann nützlich, wenn man mit Interfaces arbeitet.

Syntax

Go
switch v := x.(type) {
case Typ1:
    // v ist vom Typ Typ1
case Typ2:
    // v ist vom Typ Typ2
default:
    // v ist vom ursprünglichen Typ
}

Schauen wir uns das Ganze an einem Beispiel an.

Go Beispiel
package main

import "fmt"

func describe(i any) {
    switch v := i.(type) {
    case int, int64:
        fmt.Printf("Ganzzahl: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case bool:
        fmt.Printf("Boolean: %t\n", v)
    default:
        fmt.Printf("Unbekannter Typ: %T\n", v)
    }
}

func main() {
    describe(42)
    describe("Hallo")
    describe(true)
    describe(3.14)
}
Output
Ganzzahl: 42
String: Hallo
Boolean: true
Unbekannter Typ: float64

Wichtig:

  • i.(type) kann nur in switch Anweisungen verwendet werden
  • In jedem case hat v automatisch den richtigen Typ (keine manuelle Type-Assertion nötig)

default in Switch

Der default Fall wird ausgeführt, wenn keine der case Bedingungen zutrifft.

Eigenschaften:

  • default ist optional
  • default kann an beliebiger Stelle im switch stehen (üblich jedoch am Ende)
  • Es kann nur einen default geben
Go Beispiel
package main

import "fmt"

func main() {
    x := 10

    switch x {
    case 1:
        fmt.Println("Eins")
    case 5:
        fmt.Println("Fünf")
    default:
        fmt.Println("Etwas anders:", x)
    case 20:
        fmt.Println("Zwanzig")
    }
}
Output
Etwas anderes: 10

for - Die einzige Schleife

Go hat nur eine Schleife: for. Diese ist jedoch so flexibel, dass sie alle gängigen Schleifen-Patterns abdeckt.

  • Klassische C-Style-Schleife: (for i := 0; i < 10; i++)
  • Bedingungsschleife (while Ersatz)
  • Endlosschleife
  • Range-Schleife (über Collections iterieren)

Klassische Schleife (C-Style)

Syntax

Go
for initialisierung; bedingung; post {
    // Code
}

Gehen wir kurz auf beteiligten Komponenten ein.

  • Initialisierung: Wird einmal am Anfang ausgeführt (z.B. i := 0)
  • Bedingung: Wird vor jeder Iteration geprüft (Schleife läuft, solange true)
  • Post: Wird nach jeder Iteration ausgeführt (z.B. i++)
Go Beispiel
package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println("Iteration:", i)
    }
}
Output
Iteration: 0
Iteration: 1
Iteration: 2
Iteration: 3
Iteration: 4

Die Variable i (in diesem Beispiel) existiert nur innerhalb der Schleife. Scope von Schleifenvariablen

Go
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// i ist hier nicht mehr verfügbar!

Mehrere Variablen

Es besteht auch die Möglichkeit, for-Schleifen mit mehreren Variablen zu verwenden.

Go
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
    fmt.Printf("i=%d, j=%d\n", i, j)
}

An dieser Stelle lohnt sich ein genauer Blick auf das, was hier passiert.

Initialisierung (einmalig am Anfang)

Go
i, j := 0, 10

Hier werden zwei Variablen deklariert und initialisiert.

  • i erhält den Wert 0
  • j erhält den Wert 10

Dieser Vorgang geschieht nur einmal vor dem ersten Schleifendurchlauf.

Erste Prüfung der Bedingung

Go
i < j // 0 < 10 => true

Die Schleife wird ausgeführt, da die Bedingung wahr ist.

Erster Schleifendurchlauf

Go
fmt.Printf("i=%d, j=%d\n", i, j)

// Ausgabe: i=0, j=10

Nachiteration

Go
i, j = i+1, j-1

// i = 0+1 = 1
// j = 10-1 = 9

Die Besonderheit bei dieser Art der Schleife ist, dass die Werte gleichzeitig aktualisiert werden und die Schleife terminiert, wenn i und j sich in der Mitte treffen.

Condition-only Schleife (while Ersatz)

Go hat keine while Schleife, aber for mit nur einer Bedingung funktioniert identisch.

Syntax

Go
for bedingung {
    // Code
}

Für diesen Fall (while Ersatz) wird in der Regel ein Wert außerhalb der Schleife definiert.

Go Beispiel
package main

import "fmt"

func main() {
    i := 0
    for i < 5 {
        fmt.Println("i=", i)
        i++
    }
}
Output
i= 0
i= 1
i= 2
i= 3
i= 4

Endlos Schleife

Eine Schleife ohne Bedingung läuft unendlich (bis break oder return).

Syntax

Go
for {
    // Code läuft unendlich
}

In der Regel wird so eine Art von Code bei Event-basierten Applikationen benötigt, wie beispielsweise Desktop-Apps, die in einer Dauerschleife auf Events reagieren müssen.

Auch für Server-Loops (z.B. HTTP-Server) oder Worker-Prozesse werden diese Schleifen eingesetzt.

Go Beispiel
package main

import (
    "fmt"
    "time"
)

func main() {
    count := 0
    for {
        fmt.Println("Läuft ...", count)
        count++

        if count >= 3 {
            break
        }

        time.Sleep(1 * time.Second)
    }

    fmt.Println("Schleife beendet")
}
Output
Läuft ... 0
Läuft ... 1
Läuft ... 2
Schleife beendet

range - Iteration über Collections

range ist ein spezielles Schlüsselwort, um über Arrays, Slices, Maps, Strings und Channels zu iterieren.

range über Slices und Arrays

Syntax

Go
for index, value := range collection {
    // index = Index des Elements
    // value = Wert des Elements
}

Sowohl Index als auch Wert können einzeln weggelassen (durch _) werden, wenn einer dieser Werte nicht benötigt wird.

Werfen wir einen Blick auf ein grundlegendes Beispiel.

Go Beispiel
package main

import "fmt"

func main() {
    nums := []int{10, 20, 30, 40, 50}

    for index, value := range nums {
        fmt.Printf("Index: %d | Wert: %d\n", index, value)
    }
}
Output
Index: 0 | Wert: 10
Index: 1 | Wert: 20
Index: 2 | Wert: 30
Index: 3 | Wert: 40
Index: 4 | Wert: 50

Würden wir beispielsweise nur Indexe benötigen, könnten wir die Variable für den Wert einfach weglassen.

Go
for index := range nums {
    fmt.Println("Index:", index)
}

Wenn wir allerdings nur den Wert, ohne Index, benötigen, müssen wir die Index-Variable durch _ (Unterstrich-Platzhalter) ersetzen.

Go
for _, value := range nums {
    fmt.Println("Wert:", value)
}

Wichtig: range gibt Kopien der Werte zurück, keine Referenzen. Änderungen an value ändern nicht das Original-Slice. Somit kann oft folgender Fehler passieren, wenn man versucht Werte in einem Array/Slice zu ändern.

Go Beispiel - ❌ Falsch
package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}

    for _, value := range nums {
        value = value * 2
    }

    fmt.Println(nums)
}
Output
[1 2 3]

Wie wir sehen können, wurden die Elemente im Slice nicht aktualisiert.

Um die Werte wirklich im ursprünglichen Slice zu aktualisieren, müsste man in diesem Fall mit Indexen arbeiten.

Go Beispiel - ✅ Korrekt
package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}

    for i := range nums {
        nums[i] = nums[i] * 2
    }

    fmt.Println(nums)
}
Output
[2 4 6]

range über Maps

Bei Maps liefert range den Schlüssel und den Wert.

Syntax

Go
for key, value := range map {
    // key = Schlüssel
    // value = Wert
}

Schauen wir uns direkt ein Beispiel an. Wir erstellen uns eine Beispiel-Map.

Go Beispiel
package main

import "fmt"

func main() {
    ages := map[string]int{
        "John": 40,
        "Alice": 30,
        "Tom": 28,
    }

    for name, age := range ages {
        fmt.Printf("%s ist %d Jahre alt\n", name, age)
    }
}
Output
Tom ist 28 Jahre alt
John ist 40 Jahre alt
Alice ist 30 Jahre alt

Wichtig

Die Iteration-Reihenfolge bei Maps ist NICHT garantiert. Maps sind ungeordnet in Go. Die Reihenfolge der Iteration kann sich bei jedem Programmlauf ändern (ist sogar absichtlich randomisiert seit Go 1).

Analog zur Verwendung von range mit Arrays/Slices, können wir auch hier nur eine Komponente verwenden.

Nur Schlüssel benötigt

Go
for name := range ages {
    fmt.Println("Name:", name)
}

Nur Werte benötigt

Go
for _, age := range ages {
    fmt.Println("Alter:", age)
}

range über Strings

Bei Strings iteriert range über Runes (Unicode-Codepoints), nicht Bytes.

Syntax

Go
for index, rune := range string {
    // index = Byte-Position (nicht Zeichen-Position)
    // rune = Unicode-Codepoint
}

Hier ein Beispiel.

Go Beispiel
package main

import "fmt"

func main() {
    text := "Hallo, 世界"

    for index, runeValue := range text {
        fmt.Printf("Index %d: %c (Unicode: %U)\n", index, runeValue, runeValue)
    }
}
Output
Index 0: H (Unicode: U+0048)
Index 1: a (Unicode: U+0061)
Index 2: l (Unicode: U+006C)
Index 3: l (Unicode: U+006C)
Index 4: o (Unicode: U+006F)
Index 5: , (Unicode: U+002C)
Index 6:   (Unicode: U+0020)
Index 7: 世 (Unicode: U+4E16)
Index 10: 界 (Unicode: U+754C)

Nochmals zusammengefasst

  • index ist die Byte-Position (nicht die Zeichen-Position)
  • startet bei Index 7, bei Index 10, weil 3 Bytes benötigt
  • runeValue ist vom Typ rune (alias für int32)

Warum ist es wichtig zu beachten? Strings in Go sind UTF-8 kodiert. Ein einzelnes Unicode-Zeichen kann mehrere Bytes belegen.

range über Channels

Bei Channels iteriert range, bis der Channel geschlossen wird.

Syntax

Go
for value := range channel {
    // Empfängt Werte bis Channel geschlossen wird
}

Verdeutlichen wir das Ganze an einem einfachen Beispiel.

Go Beispiel
package main

import "fmt"

func main() {
    ch := make(chan int, 5)

    // Sender
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- 1
        }
        close(ch)
    }()

    // Empfänger
    for value := range ch {
        fmt.Println("Empfangen:", value)
    }

    fmt.Println("Channel geschlossen")
}
Output
Empfangen: 1
Empfangen: 1
Empfangen: 1
Empfangen: 1
Empfangen: 1
Channel geschlossen

Die Schleife läuft, bis der Channel mit close() geschlossen wird. Wenn der Channel nie geschlossen wird, läuft die Schleife ewig (Deadlock).

Kontrollfluss-Anweisungen

break - Schleife verlassen

break beendet die innerste Schleife oder den switch Block sofort.

Beispiel in Schleife

Go
package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        if i == 5 {
            break
        }

        fmt.Println("i =", i)
    }

    fmt.Println("Schleife beendet")
}
Output
i = 0
i = 1
i = 2
i = 3
i = 4
Schleife beendet

Beispiel in verschachtelten Schleifen

break verlässt nur die innerste Schleife.

Go
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                break
            }

            fmt.Printf("i=%d, j=%d\n", i, j)
        }
    }
}
Output
i=0, j=0
i=1, j=0
i=2, j=0

In diesem Beispiel sehen wir, dass die Schleife in dem Moment verlassen wird, in dem j den Wert 1 angenommen hat. Folglich kriegen wir in der Ausgabe immer j = 0. Dabei läuft die äußere Schleife weiter.

continue - Zur nächsten Iteration

continue überspringt den Rest der aktuellen Iteration und springt zur nächsten.

Go Beispiel
package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        if i % 2 == 0 {
            continue
        }

        fmt.Println("Ungerade:", i)
    }
}
Output
Ungerade: 1
Ungerade: 3
Ungerade: 5
Ungerade: 7
Ungerade: 9

Hier wird bei i mit einem geraden Wert continue ausgeführt. Der Rest des Schleifenkörpers wird übersprungen und die Schleife springt zur nächsten Iteration.

Labels - benannte Kontrollfluss-Ziele

Labels erlaubt es, break und continue auf äußere Schleifen anzuwenden.

Syntax

Go
LabelName:
for ... {
    for ... {
        break LabelName // Verlässt die äußere Schleife
    }
}

Schauen wir uns dies an einem Beispiel an.

Go Beispiel
package main

import "fmt"

func main() {
    outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Println("i=%d, j=%d\n", i, j)

            if i == 1 && j == 1 {
                break outer
            }
        }
    }

    fmt.Println("Fertig")
}
Output
i=%d, j=%d
0 0
i=%d, j=%d
0 1
i=%d, j=%d
0 2
i=%d, j=%d
1 0
i=%d, j=%d
1 1
Fertig

Ebenfalls kann man continue mit Label verwenden. Dabei springt die Schleifenausführung an den angegebenen Label-Namen.

Go Beispiel
package main

import "fmt"

func main() {
    outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                continue outer
            }

            fmt.Printf("i=%d, j=%d\n", i, j)
        }
    }
}
Output
i=0, j=0
i=1, j=0
i=2, j=0

Was gibt es bei Labels zu beachten?

  • Labels müssen mit einem Doppelpunkt enden (outer:)
  • Label-Namen folgen denselben Regeln wie Variablennamen

goto - Unbedingter Sprung

goto springt zu einem Label im selben Funktionskörper.

Syntax

Go
goto LabelName

LabelName:
    // Code hier

Ein Beispiel zur Verdeutlichung.

Go Beispiel
package main

import "fmt"

func main() {
    i := 0

loop:
    fmt.Println("i =", i)
    i++

    if i < 5 {
        goto loop
    }

    fmt.Println("Fertig")
}
Output
i = 0
i = 1
i = 2
i = 3
i = 4
Fertig

Die Verwendung von goto sollte nach Möglichkeit vermieden werden. Generell ist es ziemlich cool, dass Go diese Funktionalität unterstützt. Allerdings kann der Code mit goto schnell schwerer lesbar werden.

/ Weiter

Zurück zu Grundlagen

Zur Übersicht