„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Sichere Verwendung von Maps in Golang: Unterschiede in der Deklaration und Initialisierung

Sichere Verwendung von Maps in Golang: Unterschiede in der Deklaration und Initialisierung

Veröffentlicht am 08.11.2024
Durchsuche:674

Safely using Maps in Golang: Differences in declaration and initialization

Einführung

Diese Woche habe ich an einem der API-Wrapper-Pakete für Golang gearbeitet und dabei ging es um das Senden von Post-Anfragen mit URL-codierten Werten, das Setzen von Cookies und all die lustigen Dinge. Während ich jedoch den Textkörper erstellte, habe ich den Typ url.Value verwendet, um den Textkörper zu erstellen, und diesen zum Hinzufügen und Festlegen von Schlüssel-Wert-Paaren verwendet. Allerdings bekam ich in einigen Teilen einen verdrahteten Nullzeiger-Referenzfehler, ich dachte, das lag an einigen der Variablen, die ich manuell festgelegt hatte. Als ich mich jedoch näher mit dem Debuggen befasste, entdeckte ich eine häufige Gefahr oder schlechte Praxis, einen Typ nur zu deklarieren, ihn dann zu initialisieren, und das verursachte keinerlei Referenzfehler.

In diesem Beitrag werde ich behandeln, was Karten sind, wie man Karten erstellt und insbesondere wie man sie richtig deklariert und initialisiert. Erstellen Sie eine ordnungsgemäße Unterscheidung zwischen der Deklaration und Initialisierung von Karten oder ähnlichen Datentypen in Golang.

Was ist eine Karte in Golang?

Eine Karte oder eine Hashmap in Golang ist ein grundlegender Datentyp, der es uns ermöglicht, Schlüssel-Wert-Paare zu speichern. Unter der Haube handelt es sich um eine Header-Map-ähnliche Datenstruktur, die Buckets enthält, die im Grunde genommen Zeiger auf Bucket-Arrays (zusammenhängender Speicher) sind. Es verfügt über Hash-Codes, die die tatsächlichen Schlüssel-Wert-Paare speichern, und über Zeiger auf neue Buckets, wenn der aktuelle mit der Anzahl der Schlüssel überläuft. Dies ist eine wirklich intelligente Datenstruktur, die einen nahezu konstanten Zeitzugriff ermöglicht.

So erstellen Sie Karten in Golang

Um eine einfache Karte in Golang zu erstellen, können Sie ein Beispiel für einen Buchstabenhäufigkeitszähler verwenden, der eine Karte aus Zeichenfolge und Ganzzahl verwendet. Die Karte speichert die Buchstaben als Schlüssel und ihre Häufigkeit als Werte.

package main

import "fmt"

func main() {
    words := "hello how are you"
    letters := map[string]int{}

    for _, word := range words {
        wordCount[word]  
    }

    fmt.Println("Word counts:")
    for word, count := range wordCount {
        fmt.Printf("%s: %d\n", word, count)
    }
}
$ go run main.go

Word counts:
e: 2
 : 3
w: 1
r: 1
y: 1
u: 1
h: 2
l: 2
o: 3
a: 1

Wenn Sie also die Karte als map[string]int{} initialisieren, erhalten Sie eine leere Karte. Dies kann dann verwendet werden, um die Schlüssel und Werte zu füllen, wir durchlaufen die Zeichenfolge und wandeln für jedes Zeichen (Rune) dieses Zeichenbyte in die Zeichenfolge um und erhöhen den Wert. Der Nullwert für int ist also standardmäßig 0 Wenn der Schlüssel nicht vorhanden ist, ist er Null. Das ist allerdings ein zweischneidiges Schwert: Wir wissen nie, ob der Schlüssel in der Karte mit dem Wert 0 vorhanden ist, oder der Schlüssel ist nicht vorhanden, aber der Standardwert ist 0. Dafür brauchen Sie um zu prüfen, ob der Schlüssel in der Karte vorhanden ist oder nicht.

Weitere Informationen finden Sie in meinem Golang Maps-Beitrag im Detail.

Unterschied zwischen Deklaration und Initialisierung

Es gibt einen Unterschied bei der Deklaration und Initialisierung einer Variablen in einer Programmiersprache und hat viel mehr mit der Implementierung des zugrunde liegenden Typs zu tun. Bei primären Datentypen wie int, string, float usw. gibt es Default-/Nullwerte, das entspricht also der Deklaration und Initialisierung der Variablen. Im Fall von Maps und Slices stellt die Deklaration jedoch lediglich sicher, dass die Variable für den Gültigkeitsbereich des Programms verfügbar ist. Bei der Initialisierung wird sie jedoch auf ihren Standardwert/Nullwert oder den tatsächlichen Wert gesetzt, der zugewiesen werden soll.

Die Deklaration stellt die Variable also lediglich im Rahmen des Programms zur Verfügung. Bei Karten und Slices wird die Variable durch die Deklaration ohne Initialisierung auf Null gesetzt, was bedeutet, dass sie auf keinen zugewiesenen Speicher verweist und nicht direkt verwendet werden kann.

Während die Initialisierung Speicher zuweist und die Variable in einen verwendbaren Zustand versetzt. Für Karten und Slices müssen Sie sie explizit mit einer Syntax wie myMap = make(map[keyType]valueType) oder Slice = []type{} initialisieren. Ohne diese Initialisierung führt der Versuch, die Karte oder das Null-Slice zu verwenden, zu Laufzeitfehlern, wie z. B. Panik beim Zugriff auf oder beim Ändern einer Null-Map oder eines Null-Slices.

Sehen wir uns die Werte einer Karte an, wenn sie deklariert/initialisiert/nicht initialisiert wird.

Stellen Sie sich vor, Sie erstellen einen Konfigurationsmanager, der Einstellungen aus einer Karte liest. Die Karte wird global deklariert, aber erst initialisiert, wenn die Konfiguration geladen wird.

  1. Deklariert, aber nicht initialisiert

Der folgende Code zeigt einen Kartenzugriff, der nicht initialisiert ist.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings map[string]string // Declared but not initialized

func main() {
    // Attempt to get a configuration setting before initializing the map
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func getConfigSetting(key string) string {
    if configSettings == nil {
        log.Fatal("Configuration settings map is not initialized")
    }
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Server port: Setting not found
  1. Gleichzeitig deklariert und initialisiert

Der folgende Code zeigt einen Kartenzugriff, der gleichzeitig initialisiert wird.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings = map[string]string{
    "server_port":  "8080",
    "database_url": "localhost:5432",
}

func main() {
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func getConfigSetting(key string) string {
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Server port: 8080
  1. Deklariert und später initialisiert

Der folgende Code zeigt einen Kartenzugriff, der später initialisiert wird.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings map[string]string // declared but not initialized

func main() {
    // Initialize configuration settings
    initializeConfigSettings()
    // if the function is not called, the map will be nil

    // Get a configuration setting safely
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func initializeConfigSettings() {
    if configSettings == nil {
        configSettings = make(map[string]string) // Properly initialize the map
        configSettings["server_port"] = "8080"
        configSettings["database_url"] = "localhost:5432"
        fmt.Println("Configuration settings initialized")
    }
}

func getConfigSetting(key string) string {
    if configSettings == nil {
        log.Fatal("Configuration settings map is not initialized")
    }
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Configuration settings initialized
Server port: 8080

Im obigen Code haben wir die globale Karte configSettings deklariert, sie aber zu diesem Zeitpunkt nicht initialisiert, bis wir auf die Karte zugreifen wollten. Wir initialisieren die Karte in der Hauptfunktion. Diese Hauptfunktion kann andere spezifische Teile des Codes und die globale Variable configSettings eine Karte aus einem anderen Teil des Codes sein. Indem wir sie im erforderlichen Bereich initialisieren, verhindern wir, dass sie einen Nullzeiger verursacht Zugriffsfehler. Wir initialisieren die Karte nur, wenn sie Null ist, d. h. sie wurde nicht an anderer Stelle im Code initialisiert. Dadurch wird verhindert, dass die Karte überschrieben/der Konfigurationssatz aus anderen Teilen des Bereichs entfernt wird.

Fallstricke beim Zugriff auf nicht initialisierte Karten

Aber da es sich um Zeiger handelt, bringt es seine eigenen Fallstricke mit sich, z. B. den Zugriff auf Nullzeiger, wenn die Karte nicht initialisiert ist.

Sehen wir uns ein Beispiel an, einen realen Fall, in dem dies passieren könnte.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        var vals url.Values
        vals.Add("foo", "bar")
        fmt.Println(vals)
}

Dies führt zu einer Laufzeitpanik.

$ go run main.go
panic: assignment to entry in nil map

goroutine 1 [running]:
net/url.Values.Add(...)
        /usr/local/go/src/net/url/url.go:902
main.main()
        /home/meet/code/playground/go/main.go:10  0x2d
exit status 2

Das liegt daran, dass url.Values ​​eine Zuordnung von Zeichenfolgen und eine Liste von Zeichenfolgenwerten ist. Da der zugrunde liegende Typ eine Zuordnung für Werte ist und wir in dem Beispiel nur die Variable vals mit dem Typ url.Values ​​deklariert haben, zeigt sie auf eine Nullreferenz, daher die Meldung beim Hinzufügen des Werts zum Typ. Daher empfiehlt es sich, make beim Deklarieren oder Initialisieren eines Kartendatentyps zu verwenden. Wenn Sie nicht sicher sind, ob der zugrunde liegende Typ eine Karte ist, können Sie Type{} verwenden, um einen leeren Wert dieses Typs zu initialisieren.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        vals := make(url.Values)
        // OR
        // vals := url.Values{}
        vals.Add("foo", "bar")
        fmt.Println(vals)
}
$ go run urlvals.go
map[foo:[bar]]
foo=bar

Das Golang-Team empfiehlt außerdem, beim Initialisieren einer Karte die Make-Funktion zu verwenden. Verwenden Sie also entweder make für Karten, Slices und Kanäle oder initialisieren Sie die leere Wertvariable mit Type{}. Beide funktionieren ähnlich, letzteres ist jedoch allgemeiner auch auf Strukturen anwendbar.

Abschluss

Es ist für jeden Entwickler wichtig, den Unterschied zwischen der Deklaration und Initialisierung von Karten in Golang zu verstehen, nicht nur in Golang, sondern im Allgemeinen. Wie wir untersucht haben, kann die einfache Deklaration einer Map-Variablen, ohne sie zu initialisieren, zu Laufzeitfehlern führen, wie z. B. Panik beim Versuch, auf eine Null-Map zuzugreifen oder diese zu ändern. Durch die Initialisierung einer Karte wird sichergestellt, dass sie ordnungsgemäß im Speicher zugewiesen und einsatzbereit ist, wodurch diese Fallstricke vermieden werden.

Durch Befolgen von Best Practices – wie der Verwendung der Make-Funktion oder der Initialisierung mit Type{} – können Sie häufige Probleme im Zusammenhang mit nicht initialisierten Karten verhindern. Stellen Sie immer sicher, dass Karten und Slices vor der Verwendung explizit initialisiert werden, um sich vor unerwarteten Nullzeiger-Dereferenzierungen zu schützen

Vielen Dank für das Lesen dieses Beitrags. Wenn Sie Fragen, Feedback und Vorschläge haben, können Sie diese gerne in den Kommentaren hinterlassen.

Viel Spaß beim Programmieren :)

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/mr_destructive/safely-using-maps-in-golang-differences-in-declaration-and-initialization-2jfi?1 Bei Verstößen wenden Sie sich bitte an Study_golang@163 .com, um es zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3