Modul 2: Syntax-Grundlagen — Variablen, Kontrollfluss, Funktionen
Go-Syntax: Typen und Variablen (var, :=, const), Kontrollfluss (if, switch, for), Funktionen mit mehreren Rückgaben, Slices & Maps sowie Structs und Methoden.
Variablen & Typen: var, := und const
Lektion 6 von 5
Drei Wege, eine Variable zu deklarieren
In Go gibt es zwei Hauptwege plus const. Die Kurzschreibweise :=
ist der Go-Idiom — sie übernimmt den Typ automatisch vom rechten Wert.
package main
import "fmt"
func main() {
var name string = "ana" // explizit: Typ + Wert
var city string // explizit, Wert leer ""
age := 30 // Kurzform: Typ wird abgeleitet (int)
const pi = 3.14159 // Konstanten: nie geändert, nie gelöscht
fmt.Println(name, city, age, pi)
}
Die Grundtypen
Go ist statisch typisiert: Jede Variable hat einen festen Typ.
| Typ | Beispiele | Hinweis |
|---|---|---|
int, int32, int64 | 42 | Architektur-abhängig vs. feste Breite |
float64, float32 | 3.14 | float64 ist der Default |
bool | true, false | |
string | "hallo" | immutable, UTF-8 |
byte, rune | 'a', 'ä' | rune = Unicode-Zeichen (int32) |
Warum : = statt var (meist)?
Die Kurzform := ist kürzer, und der Typ steht direkt am Wert sichtbar.
var brauchst du, wenn du ohne Initialwert deklarierst (z. B. in
var list []string). Wichtig: := funktioniert nur innerhalb von
Funktionen. Auf Paketebene heißt es var.
var counter int // Paket-Level: nur var erlaubt
func main() {
x := 10 // neu deklarieren
x = x + 1 // zuweisen, kein := (x gibt es schon)
// x := 20 // FEHLER: no new variables on left side of :=
_ = x
}
Die leere Variable _ verwirft Werte bewusst — Go zwingt dich, alles zu
nutzen, was du deklarierst.
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Kontrollfluss: if, switch und die einzige Schleife for
Lektion 7 von 5
if mit Bedingung + Statement
if kann eine Initialisierung voranstellen — das Muster, das du überall
in Go-Quellcode findest:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Bitte ein Argument angeben")
return
}
name := os.Args[1]
// Go-Idiom: Statement ; Bedingung
if msg := "Hallo " + name; len(name) < 3 {
fmt.Println(msg, "(kurzer Name)")
} else {
fmt.Println(msg)
}
}
for — die einzige Schleife
Kein while, kein do-while. Alle Schleifen sind for — mit drei Varianten:
// 1) Klassisch (wie C)
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 2) Als while
n := 3
for n > 0 {
n--
}
// 3) Range über Slices, Maps, Strings (Modul 2, Lektion 9)
for i, v := range []string{"a", "b", "c"} {
fmt.Println(i, v)
}
switch — automatisches break
Go-switch hat kein Durchfallen: Am Ende jedes Case ist automatisch
break. Ein Komma listet mehrere Fälle, default ist optional.
status := "offen"
switch status {
case "offen", "in arbeit":
fmt.Println("Aktiv")
case "geschlossen":
fmt.Println("Fertig")
default:
fmt.Println("Unbekannt")
}
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Funktionen: mehrere Rückgaben, benannte Rückgaben, defer
Lektion 8 von 5
Der Go-Idiom: (wert, fehler)
Go-Funktionen können mehrere Werte zurückgeben. Zusammen mit error
als zweitem Rückgabewert entsteht das berühmte Go-Fehlermuster — explizit
statt Exceptions.
package main
import (
"fmt"
"strconv"
)
// Mehrere Rückgaben: (Ergebnis, Fehler)
func parseAge(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("keine Zahl: %q", s)
}
return n, nil
}
func main() {
age, err := parseAge("32")
if err != nil {
fmt.Println("Fehler:", err)
return
}
fmt.Println("Alter:", age)
}
Benannte Rückgabewerte
Du kannst Rückgabewerte benennen und einfach return schreiben; die
Variablen werden mit ihrem Nullwert angelegt.
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = fmt.Errorf("Division durch null")
return
}
result = a / b
return
}
defer — Aufräumen direkt am Ort
defer schiebt einen Aufruf ans Ende der Funktion. Damit steht das
Schließen direkt neben dem Öffnen — das wichtigste Go-Muster für Dateien,
Verbindungen, Locks.
import "os"
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close() // läuft am Ende der Funktion — auch bei frühzeitigem return
buf := make([]byte, 1024)
n, _ := f.Read(buf)
return string(buf[:n]), nil
}
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Slices & Maps: make, append, range
Lektion 9 von 5
Slices — das dynamische Array
[]T ist ein Slice: ein Fenster auf ein Array, das mit append wächst.
Literale, make, und das Range-Iterieren decken 99 % der Arbeit ab.
package main
import "fmt"
func main() {
// Literal
nums := []int{1, 2, 3}
// make: Slice mit Platz für 0 Elemente, Kapazität 10
items := make([]string, 0, 10)
items = append(items, "erste")
// append wächst automatisch
for i := 4; i <= 6; i++ {
nums = append(nums, i)
}
// Slicing: [vom, bis) — halb offen
tail := nums[1:3] // [2 3]
// range: Index + Wert
for i, v := range nums {
fmt.Println(i, v, cap(items))
}
_ = tail
}
Maps — der schnelle Nachschlag
map[K]V ist der assoziative Speicher. Der Doppel-Wert-Idiom v, ok
prüft, ob ein Schlüssel existiert — die Go-Antwort auf „key not found".
scores := map[string]int{"ana": 95}
scores["ben"] = 88
delete(scores, "ben")
val, ok := scores["ana"]
if !ok {
fmt.Println("fehlt")
}
fmt.Println("ana:", val)
Der große Unterschied zu anderen Sprachen
- Slices werden per Referenz übergeben (eine Änderung wirkt überall); Werte und Structs (Lektion 10) werden per Wert kopiert.
appendkann die Kapazität wechseln — hältst du eine alte Referenz, zeigt die auf den alten Speicher.
func main() {
a := []int{1, 2, 3}
b := a
b[0] = 99 // ändert auch a[0] — Slice teilt das Array
fmt.Println(a[0]) // 99
}
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Structs & Methoden: eigene Typen
Lektion 10 von 5
struct — dein eigener Typ
Ein struct bündelt Felder. Der Typ empfängt Methoden über einen
Receiver — das ist Go-Weise, Klassen zu ersetzen: Daten und Methoden, ohne
Vererbung.
package main
import "fmt"
type User struct {
Name string
Age int
}
// Wert-Receiver: arbeitet auf einer Kopie
func (u User) Greeting() string {
return "Hallo, " + u.Name
}
// Pointer-Receiver: ändert das Original
func (u *User) Birthday() {
u.Age++
}
func main() {
u := User{Name: "ana", Age: 30}
fmt.Println(u.Greeting())
u.Birthday()
fmt.Println(u.Age) // 31
}
Wert- vs. Pointer-Receiver
| Receiver | Kopie? | Wann |
|---|---|---|
func (u User) | ja (Kopie) | Read-only oder kleine Werte |
func (u *User) | nein (Original) | Zustand ändern oder große Strukturen |
Faustregel: Konsistenz — wenn eine Methode einen Pointer nimmt, dann alle Methoden des Typs, sonst verwirrt das.
Stringer: der Go-ToString
Mit String() string auf einem Wert-Receiver überschreibst du die
Standard-Ausgabe bei fmt.Println — das Go-Pendant zu toString(),
ohne Klassenhierarchie:
func (u User) String() string {
return fmt.Sprintf("%s (%d Jahre)", u.Name, u.Age)
}
// fmt.Println(u) → ana (30 Jahre)
Wochen-Check Modul 2
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren: