Cette semaine, je travaillais sur l'un des packages de wrapper d'API pour Golang, qui traitait de l'envoi de demandes de publication avec des valeurs codées en URL, de la configuration des cookies et de toutes les choses amusantes. Cependant, pendant que je construisais le corps, j'utilisais le type url.Value pour construire le corps et je l'utilisais pour ajouter et définir des paires clé-valeur. Cependant, j'obtenais une erreur de référence de pointeur nul câblé dans certaines parties, je pensais que c'était à cause de certaines des variables que j'avais définies manuellement. Cependant, en déboguant de plus près, j'ai découvert un piège courant ou une mauvaise pratique consistant simplement à déclarer un type mais à l'initialiser, ce qui ne provoquait aucune erreur de référence.
Dans cet article, je vais expliquer ce que sont les cartes, comment créer des cartes, et surtout comment les déclarer et les initialiser correctement. Créez une distinction appropriée entre la déclaration et l'initialisation des cartes ou tout type de données similaire dans Golang.
Une carte ou un hashmap en golang est un type de données de base qui nous permet de stocker des paires clé-valeur. Sous le capot, il s'agit d'une structure de données de type carte d'en-tête qui contient des compartiments, qui sont essentiellement des pointeurs vers des tableaux de compartiments (mémoire contiguë). Il contient des codes de hachage qui stockent les paires clé-valeur réelles et des pointeurs vers de nouveaux compartiments si le courant déborde du nombre de clés. Il s'agit d'une structure de données vraiment intelligente qui fournit un accès temporel presque constant.
Pour créer une carte simple en golang, vous pouvez prendre l'exemple d'un compteur de fréquence de lettres utilisant une carte de chaîne et d'entier. La carte stockera les lettres sous forme de clés et leur fréquence sous forme de valeurs.
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
Ainsi, en initialisant la carte en tant que map[string]int{}, vous obtiendrez une carte vide. Cela peut ensuite être utilisé pour remplir les clés et les valeurs, nous parcourons la chaîne et pour chaque caractère (rune), nous convertissons cet octet de caractère dans la chaîne et incrémentons la valeur, la valeur zéro pour int est 0, donc par défaut si la clé n'est pas présente, elle sera nulle, c'est un peu à double tranchant cependant, on ne sait jamais que la clé est présente dans la map avec la valeur 0 ou que la clé n'est pas présente mais la valeur par défaut est 0. Pour cela, vous devez vérifier si la clé existe ou non dans la carte.
Pour plus de détails, vous pouvez consulter mon article Golang Maps en détail.
Il y a une différence dans la déclaration et l'initialisation de n'importe quelle variable dans un langage de programmation et cela doit faire beaucoup plus avec l'implémentation du type sous-jacent. Dans le cas de types de données primaires comme int, string, float, etc., il existe des valeurs par défaut/zéro, ce qui revient donc à la déclaration et à l'initialisation des variables. Cependant, dans le cas des cartes et des tranches, la déclaration garantit simplement que la variable est disponible dans la portée du programme, mais pour l'initialisation, elle la définit sur sa valeur par défaut/zéro ou sur la valeur réelle qui doit être attribuée.
Ainsi, la déclaration rend simplement la variable disponible dans le cadre du programme. Pour les cartes et les tranches, déclarer une variable sans initialisation la définit sur zéro, ce qui signifie qu'elle ne pointe vers aucune mémoire allouée et ne peut pas être utilisée directement.
Alors que l'initialisation alloue de la mémoire et définit la variable dans un état utilisable. Pour les cartes et les tranches, vous devez les initialiser explicitement en utilisant une syntaxe telle que myMap = make(map[keyType]valueType) ou slice = []type{}. Sans cette initialisation, tenter d'utiliser la carte ou la tranche entraînera des erreurs d'exécution, telles que des paniques pour accéder ou modifier une carte ou une tranche nulle.
Regardons les valeurs d'une carte lorsqu'elle est déclarée/initialisée/non initialisée.
Imaginez que vous créez un gestionnaire de configuration qui lit les paramètres d'une carte. La carte sera déclarée globalement mais initialisée uniquement au chargement de la configuration.
Le code ci-dessous montre un accès à la carte qui n'est pas initialisé.
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
Le code ci-dessous montre un accès à la carte qui est initialisé en même temps.
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
Le code ci-dessous montre un accès à la carte qui est initialisé ultérieurement.
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
In the above code, we declared the global map configSettings but didn't initialize it at that point, until we wanted to access the map. We initialize the map in the main function, this main function could be other specific parts of the code, and the global variable configSettings a map from another part of the code, by initializing it in the required scope, we prevent it from causing nil pointer access errors. We only initialize the map if it is nil i.e. it has not been initialized elsewhere in the code. This prevents overriding the map/flushing out the config set from other parts of the scope.
But since it deals with pointers, it comes with its own pitfalls like nil pointers access when the map is not initialized.
Let's take a look at an example, a real case where this might happen.
package main import ( "fmt" "net/url" ) func main() { var vals url.Values vals.Add("foo", "bar") fmt.Println(vals) }
This will result in a runtime panic.
$ 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
This is because the url.Values is a map of string and a list of string values. Since the underlying type is a map for Values, and in the example, we only have declared the variable vals with the type url.Values, it will point to a nil reference, hence the message on adding the value to the type. So, it is a good practice to use make while declaring or initializing a map data type. If you are not sure the underlying type is map then you could use Type{} to initialize an empty value of that type.
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
It is also recommended by the golang team to use the make function while initializing a map. So, either use make for maps, slices, and channels, or initialize the empty value variable with Type{}. Both of them work similarly, but the latter is more generally applicable to structs as well.
Understanding the difference between declaring and initializing maps in Golang is essential for any developer, not just in golang, but in general. As we've explored, simply declaring a map variable without initializing it can lead to runtime errors, such as panics when attempting to access or modify a nil map. Initializing a map ensures that it is properly allocated in memory and ready for use, thereby avoiding these pitfalls.
By following best practices—such as using the make function or initializing with Type{}—you can prevent common issues related to uninitialized maps. Always ensure that maps and slices are explicitly initialized before use to safeguard against unexpected nil pointer dereferences
Thank you for reading this post, If you have any questions, feedback, and suggestions, feel free to drop them in the comments.
Happy Coding :)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!