Kontrollstrukturen
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.
Inhaltsverzeichnis
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
whileSchleife: Go hat nurfor, das aber flexibel genug ist, um alle Schleifen-Anforderungen abzudecken. - Keine
do-whileSchleife: Auch dies wird durchforabgedeckt. - Keine ternären Operatoren (
? :): Stattdessen wirdif-elseverwendet. - Kein automatisches Fallthrough in
switch: Im Gegensatz zu C/C++/Java istbreaknicht 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
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 wieifstehen (Go-Syntax-Regel)
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
if bedingung {
// Code wenn true
} else {
// Code wenn false
}Beispiel
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
if bedingung1 {
// Code wenn bedingung1 true
} else if bedingung2 {
// Code wenn bedingung2 false
} else {
// Code wenn keine Bedingung true
}Beispiel
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
if initialisierung; bedingung {
// Code
}Beispiel
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.
package main
import "fmt"
func main() {
age := 20
if age >= 18 {
fmt.Println("Volljährig, Alter:", age)
}
}Wichtige Punkte:
- Die Variable
ageexistiert nur innerhalb desifBlocks (und des zugehörigenelse) - Nach dem
ifistagenicht mehr zugänglich
Schauen wir uns ein praktisches Beispiel mit Fehlerbehandlung. Dieses Pattern wird in Go sehr oft angewendet.
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)
}
}Erfolgreich konvertiert: 123
Doppelt: 246Was passiert in diesem Beispiel?
strconv.Atoi(input)wird aufgerufen und gibt zwei Werte zurück:numunderr- Die Bedingung
err == nilprüft, ob kein Fehler aufgetreten ist - Wenn
err == nil(kein Fehler), wird derifBlock ausgeführt - Die Variablen
numunderrsind nur imifundelseBlock verfügbar
Verschachtelte if-Anweisungen
if Anweisungen können beliebig verschachtelt werden.
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")
}
}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.
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)
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.
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.
x := 5
if x {
fmt.Println("x ist wahr")
}Dagegen würde dieser Code korrekt funktionieren.
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üssentruesein)||- logisches ODER (mindestens eine Seite musstruesein)!- logisches NICHT (negiert den Wahrheitswert)
Beispiel
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 Seitefalseist, wird die rechte Seite nicht ausgewertet - Bei
||: Wenn die linke Seitetrueist, wird die rechte Seite nicht ausgewertet
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
switch ausdruck {
case wert1:
// Code wenn ausdruck == wert1
case wert2:
// Code wenn ausdruck == wert2
default:
// Code wenn keine case-Bedingung
}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
- Der Wert von
daywird ausgewertet (“Montag”) - Go vergleicht
daymit jedemcasevon oben nach unten - Der erste zutreffende
casewird ausgeführt - Kein automatisches Fallthrough - nach dem
casewird derswitchautomatisch beendet (keinbreaknö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
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.
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")
}
}Guten MorgenWas 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:
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
switch {
case bedingung1:
// Code
case bedingung2:
// Code
default:
// Code
}Jeder case enthält seine eigene Bedingung (anstatt einen Vergleichswert).
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-elseKetten - Jeder
casekann eine unterschiedliche Bedingung haben - Erster zutreffender
casewird ausgeführt
Fallthrough - Explizites Durchfallen
Wenn man das Fallthrough-Verhalten von C/Java benötigt, kann man das Schlüsselwort fallthrough verwenden.
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")
}
}Eins
Zwei
DreiWas ist hier passiert?
case 1trifft zu => “Eins” wird ausgegebenfallthroughsorgt dafür, dasscase 2ebenfalls ausgeführt wird (ohne Bedingungsprüfung)- Wieder
fallthrough=>case 3wird ausgeführt - Kein
fallthroughmehr =>switchendet
Wichtig zu merken:
fallthroughführt den nächstencasebedingungslos aus (ohne diecaseBedingung zu prüfen)fallthroughkann nicht im letztencaseoderdefaultverwendet werdenfallthroughwird 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
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.
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)
}Ganzzahl: 42
String: Hallo
Boolean: true
Unbekannter Typ: float64Wichtig:
i.(type)kann nur inswitchAnweisungen verwendet werden- In jedem
casehatvautomatisch 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:
defaultist optionaldefaultkann an beliebiger Stelle imswitchstehen (üblich jedoch am Ende)- Es kann nur einen
defaultgeben
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")
}
}Etwas anderes: 10for - 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 (
whileErsatz) - Endlosschleife
- Range-Schleife (über Collections iterieren)
Klassische Schleife (C-Style)
Syntax
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++)
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println("Iteration:", i)
}
}Iteration: 0
Iteration: 1
Iteration: 2
Iteration: 3
Iteration: 4Die Variable i (in diesem Beispiel) existiert nur innerhalb der Schleife.
Scope von Schleifenvariablen
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.
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)
i, j := 0, 10Hier werden zwei Variablen deklariert und initialisiert.
ierhält den Wert0jerhält den Wert10
Dieser Vorgang geschieht nur einmal vor dem ersten Schleifendurchlauf.
Erste Prüfung der Bedingung
i < j // 0 < 10 => trueDie Schleife wird ausgeführt, da die Bedingung wahr ist.
Erster Schleifendurchlauf
fmt.Printf("i=%d, j=%d\n", i, j)
// Ausgabe: i=0, j=10Nachiteration
i, j = i+1, j-1
// i = 0+1 = 1
// j = 10-1 = 9Die 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
for bedingung {
// Code
}Für diesen Fall (while Ersatz) wird in der Regel ein Wert außerhalb der Schleife definiert.
package main
import "fmt"
func main() {
i := 0
for i < 5 {
fmt.Println("i=", i)
i++
}
}i= 0
i= 1
i= 2
i= 3
i= 4Endlos Schleife
Eine Schleife ohne Bedingung läuft unendlich (bis break oder return).
Syntax
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.
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")
}Läuft ... 0
Läuft ... 1
Läuft ... 2
Schleife beendetrange - 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
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.
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)
}
}Index: 0 | Wert: 10
Index: 1 | Wert: 20
Index: 2 | Wert: 30
Index: 3 | Wert: 40
Index: 4 | Wert: 50Würden wir beispielsweise nur Indexe benötigen, könnten wir die Variable für den Wert einfach weglassen.
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.
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.
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
for _, value := range nums {
value = value * 2
}
fmt.Println(nums)
}[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.
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
for i := range nums {
nums[i] = nums[i] * 2
}
fmt.Println(nums)
}[2 4 6]range über Maps
Bei Maps liefert range den Schlüssel und den Wert.
Syntax
for key, value := range map {
// key = Schlüssel
// value = Wert
}Schauen wir uns direkt ein Beispiel an. Wir erstellen uns eine Beispiel-Map.
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)
}
}Tom ist 28 Jahre alt
John ist 40 Jahre alt
Alice ist 30 Jahre altWichtig
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
for name := range ages {
fmt.Println("Name:", name)
}Nur Werte benötigt
for _, age := range ages {
fmt.Println("Alter:", age)
}range über Strings
Bei Strings iteriert range über Runes (Unicode-Codepoints), nicht Bytes.
Syntax
for index, rune := range string {
// index = Byte-Position (nicht Zeichen-Position)
// rune = Unicode-Codepoint
}Hier ein 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)
}
}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
indexist die Byte-Position (nicht die Zeichen-Position)世startet bei Index 7,界bei Index 10, weil世3 Bytes benötigtruneValueist vom Typrune(alias fürint32)
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
for value := range channel {
// Empfängt Werte bis Channel geschlossen wird
}Verdeutlichen wir das Ganze an einem einfachen 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")
}Empfangen: 1
Empfangen: 1
Empfangen: 1
Empfangen: 1
Empfangen: 1
Channel geschlossenDie 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
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println("i =", i)
}
fmt.Println("Schleife beendet")
}i = 0
i = 1
i = 2
i = 3
i = 4
Schleife beendetBeispiel in verschachtelten Schleifen
break verlässt nur die innerste Schleife.
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)
}
}
}i=0, j=0
i=1, j=0
i=2, j=0In 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.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i % 2 == 0 {
continue
}
fmt.Println("Ungerade:", i)
}
}Ungerade: 1
Ungerade: 3
Ungerade: 5
Ungerade: 7
Ungerade: 9Hier 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
LabelName:
for ... {
for ... {
break LabelName // Verlässt die äußere Schleife
}
}Schauen wir uns dies an einem Beispiel an.
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")
}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
FertigEbenfalls kann man continue mit Label verwenden. Dabei springt die Schleifenausführung an den angegebenen Label-Namen.
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)
}
}
}i=0, j=0
i=1, j=0
i=2, j=0Was 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
goto LabelName
LabelName:
// Code hierEin Beispiel zur Verdeutlichung.
package main
import "fmt"
func main() {
i := 0
loop:
fmt.Println("i =", i)
i++
if i < 5 {
goto loop
}
fmt.Println("Fertig")
}i = 0
i = 1
i = 2
i = 3
i = 4
FertigDie 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.