navigation Navigation


Inhaltsverzeichnis

Array


Das Array ist ein Datentyp in Go, der eine feste Anzahl von Elementen desselben Typs speichert. Arrays bieten schnellen, indexbasierten Zugriff auf ihre Werte und werden häufig für die effiziente Verwaltung und Verarbeitung von Daten verwendet, deren Größe zur Kompilierzeit bekannt ist.

Inhaltsverzeichnis

    Einführung

    Arrays sind grundlegende Datenstrukturen in Go und bilden das Fundament für viele andere zusammengesetzte Datentypen. Ein Array ist eine numerische Sequenz von Elementen eines bestimmten Typs mit einer festen Länge. Anders als in einigen anderen Programmiersprachen ist die Länge eines Arrays in Go Teil seines Typs, was bedeutet, dass Arrays mit unterschiedlichen Längen als verschiedene Typen betrachtet werden. Ein Array mit 5 Elementen ([5]int) und ein Array mit 10 Elementen ([10]int) sind vollkommen verschiedene Typen, auch wenn beide Integer-Werte speichern.

    Ein fundamentaler Unterschied zu einigen anderen Sprachen ist, dass Arrays in Go Wert-Typen (value types) sind. Das heißt, wenn man ein Array einer anderen Variable zuweist oder es als Parameter an eine Funktion übergibt, wird eine vollständige Kopie des gesamten Arrays erstellt. Diese Semantik hat weitreichende Auswirkungen auf die Art und Weise, wie man mit Arrays arbeitet.

    Hier ein grundlegendes Beispiel für Arrays.

    Grundlegendes Beispiel
    package main
    
    import "fmt"
    
    func main() {
    
        // Diese Arrays haben verschiedene Typen
        var smallArr [3]int
        var bigArr [5]int
        var strArr [3]string
    
        fmt.Printf("Typ von 'smallArr': %T\n", smallArr)
        fmt.Printf("Typ von 'bigArr': %T\n", bigArr)
        fmt.Printf("Typ von 'strArr': %T\n", strArr)
    
    }
    Typ von 'smallArr': [3]int
    Typ von 'bigArr': [5]int
    Typ von 'strArr': [3]string

    Grundlagen

    Speicher-Layout und Performance

    Ein weiterer entscheidender Aspekt von Go-Arrays ist ihr vorhersagbares Speicher-Layout. Arrays werden als zusammenhängende Blöcke im Speicher alloziert, wobei jedes Element direkt neben dem vorherigen liegt. Diese Anordnung ist nicht nur theoretisch elegant, sondern hat praktische Auswirkungen auf die Performance.

    Moderne Computer-Architekturen sind darauf optimiert, mit zusammenhängenden Speicherbereichen zu arbeiten. Wenn man auf ein Array-Element zugreift, lädt der Prozessor automatisch die umgebenden Daten in den Cache. Das bedeutet, dass sequenzielle Zugriffe auf Array-Elemente extrem schnell sind, da die meisten Zugriffe aus dem schnellen CPU-Cache bedient werden können, anstatt den langsamen Hauptspeicher zu verwenden.

    Die feste Größe von Arrays ermöglicht es dem Go-Compiler auch, aggressive Optimierungen durchzuführen. Da die Größe zur Compile-Zeit bekannt ist, kann der Compiler oft die Bounds-Checks eliminieren oder optimieren, Stack-Allokationen verwenden anstatt Heap-Allokationen und die Speicher-Layouts für optimale Cache-Performance arrangieren.

    Beispiel - Speicher-Blöcke
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    func main() {
        nums := []int{10, 20, 30, 40, 50}
    
        for i := range nums {
            addr := unsafe.Pointer(&nums[i])
            fmt.Printf("nums[%d] = %2d @ %p\n", i, nums[i], addr)
        }
    
        fmt.Printf("Array-Größe: %d Bytes (5 x %d)\n", unsafe.Sizeof(nums), unsafe.Sizeof(nums[0]))
    }
    nums[0] = 10 @ 0x14000136000
    nums[1] = 20 @ 0x14000136008
    nums[2] = 30 @ 0x14000136010
    nums[3] = 40 @ 0x14000136018
    nums[4] = 50 @ 0x14000136020
    Array-Größe: 24 Bytes (5 x 24)

    Wert-Semantik

    Die Wert-Semantik ist vielleicht das wichtigste Konzept, das man über Array verstehen muss. Im Gegensatz zu Referenz-Typen, wo eine Variable einen Pointer auf die eigentlichen Daten enthält, enthalten Array-Variablen in Go die tatsächlichen Daten direkt. Das bedeutet, dass jede Zuweisung eine vollständige Kopie aller Array-Element erstellt.

    Diese Semantik hat weitreichende Auswirkungen auf das Verhalten des Codes. Wenn man ein Array einer anderen Variable zuweist oder es als Parameter an eine Funktion übergibt, arbeitet man mit einer unabhängigen Kopie. Änderungen an dieser Kopie haben keine Auswirkungen auf das ursprüngliche Array.

    Die Wert-Semantik macht den Code vorhersagbarer und sicherer vor unbeabsichtigten Seiteneffekten. Man kann ein Array an eine Funktion übergeben, ohne sich Sorgen machen zu müssen, dass die Funktion die ursprünglichen Daten verändert.

    Zum Vergleich, hier ein Code-Beispiel zur Erstellung einer Kopie eines Arrays in JavaScript.

    Beispiel - JavaScript
    const arrOne = [10, 20, 30];
    const arrCopy = arrOne;
    
    // Änderungen an arrCopy
    arrCopy[3] = 40;
    console.log("arrCopy:", arrCopy);
    
    console.log("arrOne:", arrOne);
    arrCopy: [ 10, 20, 30, 40 ]
    arrOne: [ 10, 20, 30, 40 ]

    Wie man in diesem Beispiel sehen kann, wird durch die Veränderung des Kopie-Arrays auch das ursprüngliche Array modifiziert, da es sich bei beiden Variablen um eine Referenz auf die gleichen Daten handelt.

    Auch, wenn man beispielsweise ein Array in JavaScript an eine Funktion übergibt und es dort ändert, wird auch das usprüngliche Array modifiziert.

    Beispiel - JavaScript: Änderung in Funktion
    const origArr = [10, 20, 30, 40];
    console.log("Original:", origArr);
    
    function changeArray(a) {
        a[0] = 1000;
        console.log("In Funktion:", a);
    }
    
    changeArray(origArr)
    console.log("Nach Funktion:", origArr);
    Original: [ 10, 20, 30, 40 ]
    In Funktion: [ 1000, 20, 30, 40 ]
    Nach Funktion: [ 1000, 20, 30, 40 ]

    Hier ein Beispiel in Go. In Go wird immer mit einer vollständigen Kopie der Daten gearbeitet. Sowohl bei direkter Kopie als auch bei Übergabe in eine Funktion.

    Beispiel - Go
    package main
    
    import "fmt"
    
    func main() {
        origArr := [4]string{"Eins", "Zwei", "Drei", "Vier"}
    
        // Kopie erstellen
        copyArr := origArr
        copyArr[0] = "100"
        copyArr[1] = "200"
        
        fmt.Println("Original:", origArr)
        fmt.Println("Kopie:", copyArr)
    
        changeArr(origArr)
        fmt.Println("Nach Funktionsaufruf:", origArr)
    }
    
    func changeArr(myArr [4]string) {
        myArr[2] = "Neuer Wert"
        fmt.Println("In Funktion:", myArr)
    }
    Original: [Eins Zwei Drei Vier]
    Kopie: [100 200 Drei Vier]
    In Funktion: [Eins Zwei Neuer Wert Vier]
    Nach Funktion: [Eins Zwei Drei Vier]

    Wie man hier sehen kann, wird das Original-Array weder nach Änderungen an der Kopie, noch nach Änderungen innerhalb einer Funktion geändert.

    Syntax und Deklaration

    Deklarationsmuster

    Go bietet mehrere Syntaxvarianten für die Array-Deklaration, die jeweils für verschiedene Situationen optimiert sind.

    Die grundlegendste Form ist die explizite Deklaration mit Typ und Größe. Wenn man ein Array ohne explizite Initialisierung deklariert, wird es automatisch mit den Null-Werten des Elementtyps gefüllt. Für numerisch Typen ist das 0, für Strings ist es der leere String, für Booleans ist es false. Diese Automatische Null-Initialisierung ist ein wichtiges Sicherheitsmerkmal von Go, das verhindert, dass man versehentlich uninitialisierte Speicherbereiche liest.

    Die verkürzte Syntax mit Typ-Inferenz (array := [5]int{...}) ist oft praktischer und lesbarer, besonders wenn man das Array sofort mit Werten initialisiert. Go kann den Typ aus den bereitgestellten Werten ableiten.

    Für die vollständige Initialisierung kann man alle Elemente explizit angeben. Diese Methode ist am klarsten, wenn man alle Werte kennt und das Array nicht später modifizieren wird. Besonders nützlich für Konstanten-ähnliche Daten, wie Wochentage, Monatsnamen oder andere feste Listen.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
        // Explizite Deklaration
        var nums [5]int
        var strings [3]string
        var flags [4]bool
    
        fmt.Println("Null-initialierte Arrays:")
        fmt.Printf("Zahlen: %v\n", nums)
        fmt.Printf("Texte: %v\n", strings)
        fmt.Printf("Flags: %v\n", flags)
    
        // Vollständige Initialisierung
        var weekDays [7]string = [7]string{
            "Montag",
            "Dienstag",
            "Mittwoch",
            "Donnerstag",
            "Freitag",
            "Samstag",
            "Sonntag",
        }
    
        // Verkürzte Syntax
        fourStrings := [4]string{
            "String one",
            "String two",
            "String three",
            "String four",
        }
    
        fmt.Println(weekDays)
        fmt.Println(fourStrings)
    }
    Null-initialierte Arrays:
    Zahlen: [0 0 0 0 0]
    Texte: [  ]
    Flags: [false false false false]
    [Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag]
    [String one String two String three String four]

    Automatische Größenerkennung

    Die Ellipsis-Syntax (...) ist eine der elegantesten Features von Go’s Array-Deklaration. Sie instruiert den Compiler, die Array-Länge automatisch basierend auf der Anzahl der bereitgestellten Initialisierer zu bestimmen. Diese Syntax löst ein häufiges Problem: Wenn man ein Array mit vielen Elementen hat und später Elemente hinzufügen oder entfernen möchte, muss man normalerweise sowohl die Elementenliste als auch die explizite Größenangabe aktualisieren. Mit der Ellipsis-Syntax muss man nur die Elementenliste ändern.

    Diese Funktion ist besonders wertvoll bei der Deklaration von Lookup-Tabellen, Konstanten-Arrays oder anderen Datenstrukturen, die sich während der Entwicklung ändern können.

    Die Ellipsis-Syntax funktioniert zur Compile-Zeit, nicht zur Laufzeit. Der Compiler zählt die bereitgestellten Elemente und ersetzt das ... durch die entsprechende Zahl. Das resultierende Array hat eine feste Größe, genau wie bei der expliziten Deklaration.

    Beispiel - Ellipsis
    package main
    
    import "fmt"
    
    func main() {
        primeNums := [...]int{2, 3, 5, 7, 11, 13, 17, 19}
    
        fmt.Printf("Anzahl der Elemente: %d\n", len(primeNums))
        fmt.Printf("Erste drei: %d, %d, %d\n", primeNums[0], primeNums[1], primeNums[2])
        fmt.Printf("Letzte drei: %d, %d, %d\n",
            primeNums[len(primeNums)-3]
            primeNums[len(primeNums)-2]
            primeNums[len(primeNums)-1])
    }
    Anzahl der Elemente: 8
    Erste drei: 2, 3, 5
    Letzte drei: 13, 17, 19

    Index-spezifische Initialisierung

    Go’s Index-spezifische Initialisierung ist ein mächtiges Feature, das es ermöglicht, nur bestimmte Array-Positionen zu setzen, während der Rest mit Null-Werten gefüllt wird. Diese Syntax ist besonders nützlich für sparse Arrays, Lookup-Tabellen oder wenn man ein großes Array hat, aber nur wenige spezifische Werte setzen möchte.

    Diese Syntax verwendet das Format index: wert innerhalb der geschweiften Klammern. Man kann Index-spezifische und sequenzielle Initialisierung in derselben Deklaration mischen. Wenn man einen Index explizit setzt, werden nachfolgende Werte ab dem nächsten Index fortgesetzt.

    Diese Funktionalität ist nützlich für die Erstellung von Mapping-Tabellen, wo bestimmte Indizes spezielle Bedeutungen haben. Zum Beispiel kann man ein Array für Wochentage erstellen, wo Index 1 Montag entspricht, Index 2 Dienstag, usw. und Index 0 für ungültige oder unbekannte Eingaben reserviert ist.

    Im folgenden Beispiel sieht man die Basic-Deklaration mit spezifischen Index-Werten.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
        // Sparse Array - nur bestimmte Positionen setzen
        sparseArr := [10]int{
            2: 200,
            5: 500,
            9: 900,
        }
        fmt.Printf("Sparse Array: %v\n", sparseArr)
    }
    Sparse Array: [0 0 200 0 0 500 0 0 0 900]

    Hier ein Beispiel mit gemischter Initialisierung - sequenziell und Index-spezifisch.

    Beispiel - Gemischte Initialisierung
    package main
    
    import "fmt"
    
    func main() {
        mixed := [8]string{
            "Start",
            1: "Explizit_1",
            "Auto_2",
            "Auto_3",
            7: "Ende",
        }
        fmt.Println(mixed)
    }
    [Start Explizit_1 Auto_2 Auto_3    Ende]

    Im oberen Beispiel bleiben die Indexe 4, 5 und 6 leer.

    Zugriff und Iteration

    Zugriff auf Elemente und Bounds-Checking

    Der Zugriff auf Array-Elemente in Go erfolgt über die Standard-Index-Notation, aber Go fügt eine wichtige Sicherheitsschicht hinzu: Automatisches Bound-Checking zur Laufzeit. Diese Funktion unterscheidet Go von anderen Sprachen wie C oder C++, wo der Zugriff auf ungültige Array-Indizes zu undefiniertem Verhalten führen kann.

    Wenn man versucht, auf einen Index außerhalb der Array-Grenzen zuzugreifen, löst Go eine Panic aus. Das mag zunächst drastisch erscheinen, aber es ist ein bewusster Sicherheitsmechnismus. Eine Panic ist besser als korrupte Daten oder Sicherheitslücken, die durch ungültige Speicherzugriffe entstehen können.

    Die Bounds-Checks haben einen minimalistischen Performance-Overhead, da moderne Compiler sie oft optimieren können, besonders in Schleifen mit bekannten Grenzen. In vielen Fällen kann der Go-Compiler zur Compile-Zeit beweisen, dass ein Zugriff sicher ist.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
    
        grades := [5]float64{1.3, 2.7, 1.0, 3.0, 2.3}
    
        // Normaler, sicherer Zugriff auf gültige Indizes
        fmt.Printf("Erstes Element (Index 0): %.1f\n", grades[0])
        fmt.Printf("Letztes Element (Index 4): %.1f\n", grades[4])
    
        // Elemente können direkt modifiziert werden
        fmt.Printf("Vor Modifikation: %v\n", grades)
        grades[1] = 1.7
        grades[4] = 2.0
        fmt.Printf("Nach Modifikation: %v\n", grades)
    
        // Defensive Programmierung - Index vor Zugriff prüfen
        testIndizes := []int{0, 2, 5, -1, 10}
        fmt.Println("\nSicherer Zugriff auf Indizes:")
    
        for _, idx := range testIndizes {
            if idx >= 0 && idx < len(grades) {
                fmt.Printf("grades[%d] = %.1f\n", idx, grades[idx])
            } else {
                fmt.Printf("Index %d ist ungültig (gültig: 0-d%d) x\n", idx, len(grades)-1)
            }
        }
    
        // Hilfsfunktion für sicheren Zugriff
        grade, ok := safeGet(grades, 2)
        if ok {
            fmt.Printf("\nSicherer Zugriff erfolgreich: %.1f\n", grade)
        }
    
        note, ok = safeGet(grades, 10)
        if !ok {
            fmt.Println("Sicherer Zugriff fehlgeschlagen")
        }
    
    }
    
    func safeGet(arr [5]float64, idx int) (float64, bool) {
        if idx > 0 && idx < len(arr) {
            return arr[idx], true
        }
    
        return 0, false
    }
    Erstes Element (Index 0): 1.3
    Letztes Element (Index 4): 2.3
    
    Direkte Modifikation:Vor Modifikation: [1.3 2.7 1 3 2.3]
    Nach Modifikation: [1.3 1.7 1 3 2]
    
    Sicherer Zugriff auf Indizes:
    Index 0 ist ungültig (gültig: 0-4) x
    grades[2] = 1.0 ✓
    Index 5 ist ungültig (gültig: 0-4) x
    Index -1 ist ungültig (gültig: 0-4) x
    Index 10 ist ungültig (gültig: 0-4) x
    
    Sicherer Zugriff erfolgreich: 1.0
    Sicherer Zugriff fehlgeschlagen

    Range-Schleifen

    Die range Schleife ist Go’s idiomatischer Weg, über Arrays zu iterieren und sie bietet mehrere Vorteile gegenüber traditionellen Index-basierten Schleifen. Sie ist nicht nur syntaktisch sauberer, sondern auch sicherer, da sie automatisch die korrekte Array-Länge verwendet und damit Index-out-of-bounds Fehler verhindert.

    Die range Schleife funktioniert, indem sie bei jedem Durchlauf den aktuellen Index und den entsprechenden Wert zurückgibt. Man kann wählen, ob man beide Werte, nur den Index oder nur den Wert verwenden möchte. Diese Flexibilität macht sie für verschiedene Iterationsmuster geeignet.

    Ein wichtiger Aspekt der range Schleife ist, dass der Wert eine Kopie des Array-Elements ist, nicht eine Referenz. Das bedeutet, wenn man den Wert in der Schleife modifiziert, ändert man nicht das ursprüngliche Array-Element. Wenn man Array modifizieren möchte, muss man den Index verwenden, um direkt auf das Array-Element zuzugreifen.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
        cities := [6]string{
            "Berlin",
            "München",
            "Hamburg",
            "Köln",
            "Frankfurt",
            "Stuttgart",
        }
    
        // 1. Vollständige Iteration mit Index und Wert
        fmt.Println("\n1. Index und Wert verwenden":)
        for idx, city := range cities {
            fmt.Printf("\tPosition %d: %s (%d Zeichen)\n", idx, city, len(city))
        }
    
        // 2. Nur Werte verwenden (Index ignorieren mit _)
        fmt.Println("\n2. Nur Werte (Index ignoriert):")
        for _, city := range cities {
            if len(city) > 6 {
                fmt.Printf("\tLanger Stadtname: %s\n", city)
            }
        }
    
        // 3. Nur Indizes verwenden (Wert ignorieren)
        fmt.Println("\n3. Nur Indizes (Wert ignoriert):")
        for idx, _ := range cities {
            if idx%2 == 0 {
                fmt.Printf("\tGerade Position %d: %s\n", idx, cities[idx])
            }
        }
    
        // 4. Vergleich mit traditioneller Index-Schleife
        fmt.Println("\n4. Traditionelle Index-Schleife zum Vergleich:")
        for i := 0; i < len(cities); i++ {
            fmt.Printf("\t [%d] %s\n", i, cities[i])
        }
    
        // 5. Rückwärts Iteration (range funktioniert nicht rückwärts)
        fmt.Println("\nRückwärts-Iteration:")
        for i := len(cities) - 1; i >= 0; i-- {
            fmt.Printf("\t %s", cities[i])
            if i > 0 {
                fmt.Printf(" <- ")
            }
        }
    }
    1. Index und Wert verwenden:
        Position 0: Berlin (6 Zeichen)
        Position 1: München (8 Zeichen)
        Position 2: Hamburg (7 Zeichen)
        Position 3: Köln (5 Zeichen)
        Position 4: Frankfurt (9 Zeichen)
        Position 5: Stuttgart (9 Zeichen)
    
    2. Nur Werte (Index ignoriert):
        Langer Stadtname: München
        Langer Stadtname: Hamburg
        Langer Stadtname: Frankfurt
        Langer Stadtname: Stuttgart
    
    3. Nur Indizes (Wert ignoriert):
        Gerade Position 0: Berlin
        Gerade Position 2: Hamburg
        Gerade Position 4: Frankfurt
    
    4. Traditionelle Index-Schleife zum Vergleich:
        [0] Berlin
        [1] München
        [2] Hamburg
        [3] Köln
        [4] Frankfurt
        [5] Stuttgart
    
    Rückwärts-Iteration:
        [5] Stuttgart
        [4] Frankfurt
        [3] Köln
        [2] Hamburg
        [1] München
        [0] Berlin

    Array-Vergleiche und Gleichheitstests

    Go bietet direkte Unterstützung für Array-Vergleiche, was in vielen anderen Sprachen nicht verfügbar ist oder spezielle Bibliotheksfunktionen erfordert. Dieses Funktion ist möglich, weil Arrays Wert-Typen mit bekannter, fester Größe sind. Der Compiler kann effiziente Vergleichsoperationen generieren, die oft auf spezialisierte Speicher-Vergleichsfunktionen zugreifen.

    Array-Vergleiche in Go sind Element-für-Element Vergleiche. Zwei Arrays sind gleich, wenn sie den gleichen Typ haben und alle entsprechenden Elemente gleich sind. Diese Operation ist sowohl type-safe als auch effizient, da sie zur Compile-Zeit optimiert werden kann.

    Ein wichtiger Punkt ist, dass nur Arrays des exakt gleichen Typs vergleichen werden können. Arrays verschiedener Größen sind verschiedene Typen und können nicht verglichen werden. Die Einschränkung verhindert subtile Fehler, die entstehen können, wenn Arrays unterschiedlicher Größe versehentlich verglichen werden.

    Die Vergleichsoperationen funktionieren für alle vergleichbaren Elementtypen. Das schließt Grundtypen wie Zahlen, Strings und Booleans ein, sowie Structs, die nur vergleichbare Felder enthalten.

    Arrays von nicht-vergleichbaren Typen (wie Slices, Maps oder Funktionen) können nicht verglichen werden.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
        // Grundlegende Array-Vergleiche
        arrayOne := [4]int{1, 2, 3, 4}
        arrayTwo := [4]int{1, 2, 3, 4}
        arrayThree := [4]int{1, 2, 3, 5}
    
        // Gleichheitstests
        fmt.Printf("arrayOne == arrayTwo: %t (alle Elemente gleich)\n", arrayOne == arrayTwo)
        fmt.Printf("arrayOne == arrayThree: %t (letztes Element unterschiedlich)\n", arrayOne == arrayThree)
        fmt.Printf("arrayOne != arrayThree: %t (Ungleichheitstest)\n", arrayOne != arrayThree)
    }
    arrayOne == arrayTwo: true (alle Elemente gleich)
    arrayOne == arrayThree: false (letztes Element unterschiedlich)
    arrayOne != arrayThree: true (Ungleichheitstest)

    In diesem Beispiel schauen wir uns, wie man ein Array mit Strings vergleicht.

    Beispiel - Vergleich Array mit Strings
    package main
    
    import "fmt"
    
    func main() {
        wordsOne := [3]string{"Go", "ist", "klasse"}
        wordsTwo := [3]string{"Go", "ist", "klasse"}
        wordsThree := [3]string{"Go", "ist", "super"}
    
        fmt.Printf("wordsOne == wordTwo: %t\n", wordsOne == wordsTwo)
        fmt.Printf("wordsOne == wordsThree: %t\n", wordsOne == wordsThree)
    }
    wordsOne == wordTwo: true
    wordsOne == wordsThree: false

    Beispiel - Array mit Booleans
    package main
    
    import "fmt"
    
    func main() {
        flagsOne := [4]bool{true, false, true, false}
        flagsTwo := [4]bool{true, false, true, false}
        flagsThree := [4]bool{true, false, false, true}
    
        fmt.Printf("flagsOne == flagsTwo: %t\n", flagsOne == flagsTwo)
        fmt.Printf("flagsOne == flagsThree: %t\n", flagsOne == flagsThree)
    }
    flagsOne == flagsTwo: true
    flagsOne == flagsThree: false

    Im folgenden Beispiel wird ein praktisches Beispiel gezeigt. Hier wird ein Vergleich in einem if Statement verwendet.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
        currentState := [3]string{"logged_in", "admin", "active"}
        expectedState := [3]string{"logged_in", "admin", "active"}
    
        if currentState == expectedState {
            fmt.Println("Benutzer-Status ist ok")
        } else {
            fmt.Println("Status-Mismatch: erwartet %v, aber %v\n", expectedState, currentState)
        }
    }
    Benutzer-Status ist ok

    Mehrdimensionale Arrays

    Grundlagen von mehrdimensionalen Arrays

    Mehrdimensionale Arrays in Go sind eine logische Erweiterung des Array-Konzepts, die es ermöglicht, komplexe Datenstrukturen zu modelieren. Ein zweidimensionales Array kann als “Array von Arrays” verstanden werden, aber es ist wichtig zu verstehen, dass Go diese Struktur als einen einzigen, zusammengehängenden Speicherblock implementiert.

    Die Notation [3][4]int beschreibt ein Array mit 3 “Zeilen”, wobei jede Zeile ein Array von 4 Integer ist. Obwohl wir konzeptuelle von Zeilen und Spalten sprechen, sind alle 12 Integer-Werte physisch nebeneinander im Speicher angeordnet. Diese Anordnung wird als “row-major order” bezeichnet und ist entscheidend für die Performance-Eigenschaften mehrdimensionaler Arrays.

    Der Zugriff auf Elemente erfolgt durch Verkettung der Index-Operationen: array[zeile][spalte]. Jeder Index-Zugriff ist eine separate Operation, was bedeutet, dass array[i] selbst ein Array ist - das Array der i-ten Zeile. Diese Eigenschaft ermöglicht es, mit Teilbereichen der mehrdimensionalen Struktur zu arbeiten.

    Die Typisierung mehrdimensionaler Arrays folgt den gleichen strengen Regeln wie bei eindimensionalen Arrays. [3][4]int und [4][3]int sind vollkommen verschiedene Typen, auch wenn sie die gleiche Anzahl von Elementen enthalten. Diese Typsicherheit verhindert häufige Fehler bei der Arbeit mit Matrizen und anderen geometrischen Datenstrukturen.

    Beispiel
    package main
    
    import "fmt"
    
    func main() {
    
        // Ein Spielfeld als zweidimensionales Array
        var gamePlate [3][3]string
    
        // Jede Zelle bekommt einen Platzhalter
        for i := range gamePlate {
            for j := range gamePlate[i] {
                gamePlate[i][j] = "-"
            }
        }
    
        // Ausgabe des initialisiertes Spielfeldes
        printGamePlate(gamePlate)
    
        // Spielzüge hinzufügen
        gamePlate[0][0] = "X"
        gamePlate[1][1] = "O"
        gamePlate[2][2] = "X"
        gamePlate[0][2] = "O"
        gamePlate[2][0] = "X"
    
        // Spielfeld erneut ausgeben
        fmt.Println()
        printGamePlate(gamePlate)
    
    }
    
    func printGamePlate(field [3][3]string) {
        for cell := range field {
            fmt.Print(" ")
            for col := range field[cell] {
                fmt.Printf("%s ", field[cell][col])
            }
            fmt.Println()
        }
    }
    - - - 
    - - - 
    - - -
    
    X - O 
    - O - 
    X - X