На этой неделе я работал над одним из пакетов-оболочек API для golang, который касался отправки запросов на публикацию со значениями в кодировке URL, установки файлов cookie и всех интересных вещей. Однако при создании тела я использовал тип url.Value для создания тела и использовал его для добавления и установки пар ключ-значение. Однако в некоторых частях я получал проводную ошибку ссылки на нулевой указатель, я думал, что это из-за некоторых переменных, которые я установил вручную. Однако, проведя более тщательную отладку, я обнаружил распространенную ошибку или плохую практику простого объявления типа, но его инициализации, что приводит к нулевым ошибкам ссылки.
В этом посте я расскажу, что такое карты, как их создавать и особенно как правильно их объявлять и инициализировать. Создайте правильное различие между объявлением и инициализацией карт или любого подобного типа данных в golang.
Карта или хэш-карта в golang — это базовый тип данных, который позволяет нам хранить пары ключ-значение. Под капотом это структура данных, подобная карте заголовков, которая содержит сегменты, которые по сути являются указателями на массивы сегментов (непрерывная память). Он имеет хэш-коды, в которых хранятся фактические пары ключ-значение, и указатели на новые сегменты, если текущие переполняются из-за количества ключей. Это действительно умная структура данных, обеспечивающая практически постоянный доступ во времени.
Чтобы создать простую карту в golang, вы можете взять пример счетчика частоты букв, используя карту строки и целого числа. Карта сохранит буквы как ключи, а их частоту — как значения.
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
Итак, инициализируя карту как map[string]int{}, вы получите пустую карту. Затем это можно использовать для заполнения ключей и значений, мы перебираем строку и для каждого символа (руны) добавляем этот байт символа в строку и увеличиваем значение, нулевое значение для int равно 0, поэтому по умолчанию если ключа нет, он будет равен нулю, хотя это палка о двух концах: мы никогда не знаем, присутствует ли ключ на карте со значением 0 или ключ отсутствует, но значение по умолчанию равно 0. Для этого вам нужно проверить, существует ли ключ на карте или нет.
Для получения более подробной информации вы можете подробно прочитать мою публикацию о картах Golang.
Существует разница в объявлении и инициализации любой переменной в языке программирования, и здесь нужно гораздо больше делать с реализацией базового типа. В случае первичных типов данных, таких как int, string, float и т. д., существуют значения по умолчанию/нулевые значения, так что это то же самое, что объявление и инициализация переменных. Однако в случае карт и срезов объявление просто гарантирует, что переменная доступна для области действия программы, однако для инициализации ей присваивается значение по умолчанию/нулевое значение или фактическое значение, которое должно быть присвоено.
Итак, объявление просто делает переменную доступной в рамках программы. Для карт и срезов объявление переменной без инициализации устанавливает ее в ноль, то есть она указывает на нераспределенную память и не может использоваться напрямую.
В то время как инициализация выделяет память и устанавливает переменную в пригодное для использования состояние. Для карт и срезов вам необходимо явно инициализировать их, используя синтаксис, например myMap = make(map[keyType]valueType) или срез = []type{}. Без этой инициализации попытка использовать карту или срез приведет к ошибкам во время выполнения, таким как паника при доступе или изменении нулевой карты или среза.
Давайте посмотрим на значения карты, когда она объявлена/инициализирована/не инициализирована.
Представьте, что вы создаете менеджер конфигурации, который считывает настройки с карты. Карта будет объявлена глобально, но инициализирована только при загрузке конфигурации.
Приведенный ниже код демонстрирует доступ к карте, который не инициализирован.
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
Приведенный ниже код демонстрирует одновременно инициализируемый доступ к карте.
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
Приведенный ниже код демонстрирует доступ к карте, который инициализируется позже.
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
В приведенном выше коде мы объявили глобальную конфигурацию карты, но не инициализировали ее на этом этапе, пока не захотели получить доступ к карте. Мы инициализируем карту в основной функции, эта основная функция может быть другими конкретными частями кода, а глобальная переменная configSettings - карта из другой части кода, инициализируя ее в требуемой области, мы не позволяем ей вызывать нулевой указатель ошибки доступа. Мы инициализируем карту только в том случае, если она равна нулю, т. е. она не была инициализирована где-либо еще в коде. Это предотвращает переопределение карты/очистку набора конфигурации из других частей области действия.
Но поскольку он имеет дело с указателями, у него есть свои подводные камни, такие как доступ к нулевым указателям, когда карта не инициализирована.
Давайте рассмотрим пример, реальный случай, когда это может произойти.
package main import ( "fmt" "net/url" ) func main() { var vals url.Values vals.Add("foo", "bar") fmt.Println(vals) }
Это приведет к панике во время выполнения.
$ 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
Это связано с тем, что url.Values представляет собой карту строк и список строковых значений. Поскольку базовый тип представляет собой карту для значений, а в этом примере мы объявили переменную vals только с типом url.Values, она будет указывать на нулевую ссылку, следовательно, на сообщение о добавлении значения к типу. Поэтому рекомендуется использовать make при объявлении или инициализации типа данных карты. Если вы не уверены, что базовый тип — это карта, вы можете использовать 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
Команда golang также рекомендует использовать функцию make при инициализации карты. Итак, либо используйте make для карт, срезов и каналов, либо инициализируйте переменную пустого значения с помощью Type{}. Оба они работают одинаково, но последний более применим и к структурам.
Понимание разницы между объявлением и инициализацией карт в Golang важно для любого разработчика, не только в golang, но и вообще. Как мы выяснили, простое объявление переменной карты без ее инициализации может привести к ошибкам во время выполнения, таким как паника при попытке доступа или изменения нулевой карты. Инициализация карты гарантирует, что она правильно распределена в памяти и готова к использованию, что позволяет избежать этих ошибок.
Следуя рекомендациям, таким как использование функции make или инициализация с помощью Type{}, вы можете предотвратить распространенные проблемы, связанные с неинициализированными картами. Всегда гарантируйте, что карты и фрагменты явно инициализированы перед использованием, чтобы защититься от неожиданного разыменования нулевого указателя
Спасибо, что прочитали этот пост. Если у вас есть какие-либо вопросы, отзывы и предложения, не стесняйтесь оставлять их в комментариях.
Удачного программирования :)
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3