”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 在 Golang 中安全使用 Map:声明和初始化的差异

在 Golang 中安全使用 Map:声明和初始化的差异

发布于2024-11-08
浏览:680

Safely using Maps in Golang: Differences in declaration and initialization

介绍

本周,我正在为 golang 开发一个 API 包装器包,它处理发送带有 URL 编码值的 post 请求、设置 cookie 以及所有有趣的东西。但是,当我构建主体时,我使用 url.Value 类型来构建主体,并使用它来添加和设置键值对。然而,我在某些部分遇到了有线零指针引用错误,我认为这是因为我手动设置的一些变量造成的。然而,通过仔细调试,我发现了一个常见的陷阱或不好的做法,即仅声明类型但初始化它,从而导致零引用错误。

在这篇文章中,我将介绍什么是映射、如何创建映射,特别是如何正确声明和初始化它们。在 golang 中的映射或任何类似数据类型的声明和初始化之间创建适当的区别。

Golang 中的地图是什么?

golang中的map或hashmap是一种基本数据类型,它允许我们存储键值对。在底层,它是一个类似标头映射的数据结构,用于保存存储桶,这些存储桶基本上是指向存储桶数组(连续内存)的指针。它具有存储实际键值对的哈希码,以及在当前键数量溢出时指向新存储桶的指针。这是一个非常智能的数据结构,提供几乎恒定的时间访问。

如何在 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 Maps 帖子。

声明和初始化之间的区别

在编程语言中声明和初始化任何变量都是不同的,并且必须在底层类型的实现上做更多的事情。对于 int、string、float 等主要数据类型,有默认值/零值,因此这与变量的声明和初始化相同。但是,在映射和切片的情况下,声明只是确保变量可用于程序的范围,但是对于初始化,请将其设置为其默认/零值或应分配的实际值。

因此,声明只是使变量在程序范围内可用。对于映射和切片,声明变量而不初始化会将其设置为 nil,这意味着它指向没有分配的内存并且不能直接使用。

而初始化分配内存并将变量设置为可用状态。对于映射和切片,您需要使用 myMap = make(map[keyType]valueType) 或 slice = []type{} 等语法显式初始化它们。如果没有此初始化,尝试使用映射或切片将导致运行时错误,例如访问或修改 nil 映射或切片时出现恐慌。

让我们看看声明/初始化/未初始化时映射的值。

假设您正在构建一个从地图读取设置的配置管理器。该地图将在全局声明,但仅在加载配置时初始化。

  1. 已声明但未初始化

下面的代码演示了未初始化的地图访问。

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. 同时声明和初始化

下面的代码演示了同时初始化的地图访问。

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. 声明并稍后初始化

下面的代码演示了稍后初始化的地图访问。

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,但当时没有初始化它,直到我们想要访问地图。我们在main函数中初始化map,这个main函数可以是代码的其他特定部分,而全局变量configSettings是代码其他部分的map,通过在所需的范围内初始化它,我们可以防止它导致nil指针访问错误。我们仅在映射为零时才初始化映射,即它尚未在代码中的其他地方初始化。这可以防止覆盖映射/从范围的其他部分清除配置集。

访问未初始化地图的陷阱

但是由于它处理的是指针,所以它也有自己的陷阱,比如在映射未初始化时访问 nil 指针。

让我们看一个例子,一个可能发生这种情况的真实案例。

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 是字符串和字符串值列表的映射。由于底层类型是 Values 的映射,并且在示例中,我们仅使用 url.Values 类型声明了变量 vals,因此它将指向 nil 引用,因此会出现将值添加到类型的消息。因此,在声明或初始化映射数据类型时使用 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 中,而且在一般情况下也是如此。正如我们所探讨的,简单地声明映射变量而不初始化它可能会导致运行时错误,例如尝试访问或修改 nil 映射时出现恐慌。初始化映射可确保它在内存中正确分配并可供使用,从而避免这些陷阱。

通过遵循最佳实践(例如使用 make 函数或使用 Type{} 进行初始化),您可以防止与未初始化映射相关的常见问题。始终确保映射和切片在使用前已显式初始化,以防止意外的 nil 指针取消引用

感谢您阅读这篇文章,如果您有任何问题、反馈和建议,请随时在评论中提出。

快乐编码:)

版本声明 本文转载于:https://dev.to/mr_destructive/safely-using-maps-in-golang-differences-in-declaration-and-initialization-2jfi?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 重新学习CS基础知识——实现队列
    重新学习CS基础知识——实现队列
    你曾经站在队列中吗,队列数据结构也做同样的事情。当你想在你最喜欢的自助餐厅点餐时,你站在队伍的最后,然后你就可以继续排队并离开。 CS 中的队列数据结构执行相同的功能。队列数据结构是先进先出的数据结构。队列数据结构可以使用两个基本函数 Enqueue 和 Dequeue 来构建,这两个函数基本上是...
    编程 发布于2024-11-09
  • 为 Angular 18 设置 linter 和 IDE
    为 Angular 18 设置 linter 和 IDE
    将 eslint、prettier、env 添加到应用程序中。 遗憾的是,Angular 默认情况下不会自行生成这一切。更改原理图可以提高数千个 Angular 项目的质量。 设置 eslint 9 连接 eslint: yarn ng add @angular-eslint/sch...
    编程 发布于2024-11-09
  • 使用 JavaScript 进行网页抓取和代理设置的初学者指南
    使用 JavaScript 进行网页抓取和代理设置的初学者指南
    使用JavaScript代码模拟用户操作,获取所需信息。包括模拟用户打开网页、点击链接、输入关键字等操作,并从网页中提取所需信息。 Javascript网页抓取的核心原理 使用JavaScript代码模拟用户操作来获取所需信息。包括模拟用户打开网页、点击链接、输入关键字等操作,并从网...
    编程 发布于2024-11-09
  • 在 Android 上运行 Llama:使用 Ollama 的分步指南
    在 Android 上运行 Llama:使用 Ollama 的分步指南
    Llama 3.2 最近在 Meta 开发者大会上推出,展示了令人印象深刻的多模式功能以及针对使用高通和联发科技硬件的移动设备进行优化的版本。这一突破使开发人员能够在移动设备上运行 Llama 3.2 等强大的 AI 模型,为更高效、私密和响应迅速的 AI 应用程序铺平道路。 Meta 发布了 Ll...
    编程 发布于2024-11-09
  • 如何在 Python 中格式化字符串以将它们对齐直列?
    如何在 Python 中格式化字符串以将它们对齐直列?
    以固定宽度打印字符串打印字符串时,将它们对齐成直列可以增强可读性。在 Python 中使用 format 或 f-string 提供了实现此目的的便捷方法。使用 str.format()str.format() 提供了一种简单的填充方法字符串。其语法包括占位符 {},后跟格式化表达式。对于左对齐,请...
    编程 发布于2024-11-09
  • 为什么微服务比单体架构重要
    为什么微服务比单体架构重要
    在当今快节奏的技术环境中,企业需要可扩展且灵活的解决方案来快速适应不断变化的需求。与传统的整体方法相比,这就是微服务架构的亮点。 1.什么是单体架构? 单体架构是一个单一的、统一的系统,其中所有组件都是互连和相互依赖的。这意味着对系统的任何更改或更新都需要重新构建和重新部署整个应用程...
    编程 发布于2024-11-09
  • 如何在 PHP 中访问对象属性:了解语法和错误解决方案
    如何在 PHP 中访问对象属性:了解语法和错误解决方案
    理解 PHP 对象属性访问在 PHP 中,访问对象属性对于处理复杂的数据结构至关重要。属性保存与对象关联的信息,使我们能够管理和操作该数据。访问对象属性有两种常用语法:1。 $property1此语法直接通过名称访问特定属性。它用于分配或检索各个属性的值。但是,这种方法要求您提前知道确切的属性名称。...
    编程 发布于2024-11-09
  • PDO如何防止SQL注入并替换转义单引号?
    PDO如何防止SQL注入并替换转义单引号?
    PDO防止SQL注入的方法如果你已经从mysql库过渡到PDO,你可能想知道如何替换real_escape_string用于转义发往数据库的字符串中的单引号的函数。虽然向每个字符串添加斜杠可能看起来很麻烦,但 PDO 提供了一种更有效的替代方案。PDO 准备的强大功能为了防止 SQL 注入,PDO ...
    编程 发布于2024-11-09
  • 通过“项目:使用互斥体同步多线程打印”课程释放您的编码潜力
    通过“项目:使用互斥体同步多线程打印”课程释放您的编码潜力
    您准备好深入多线程编程的世界并学习如何使用互斥体来同步字符串的打印了吗? LabEx 提供的项目:使用互斥体同步多线程打印课程就是您的最佳选择。 在这个基于项目的综合课程中,您将踏上了解互斥体在协调多线程执行方面的重要性的旅程。您将首先修改现有的“混沌打字机”程序,确保字符串以正确的顺序打印,而不会...
    编程 发布于2024-11-09
  • 为什么我在 MySQL 中收到“\'create_date\'时间戳字段的默认值无效”错误?
    为什么我在 MySQL 中收到“\'create_date\'时间戳字段的默认值无效”错误?
    “create_date”时间戳字段的默认值无效创建带有时间戳列的表并指定默认值“0000-”时00-00 00:00:00',可能会出现错误,指示“'create_date'的默认值无效”。这个错误是由 MySQL 的 SQL 模式 - NO_ZERO_DATE 引起的。根...
    编程 发布于2024-11-09
  • 尽管出现“页面已移动”错误,如何使用 cURL 检索页面内容?
    尽管出现“页面已移动”错误,如何使用 cURL 检索页面内容?
    使用 cURL 检索页面内容在此上下文中,您试图使用 cURL 抓取 Google 搜索结果页面的内容。尽管尝试设置用户代理和各种选项,但您仍无法成功检索页面内容。重定向或“页面移动”错误继续困扰着您。据信该问题可能源于查询字符串中特殊字符的编码。为了缓解这种情况,需要更改 PHP 代码。方法如下:...
    编程 发布于2024-11-09
  • 如何使用 JPA 和 Hibernate 以 UTC 格式存储日期/时间?
    如何使用 JPA 和 Hibernate 以 UTC 格式存储日期/时间?
    使用 JPA 和 Hibernate 在 UTC 时区存储日期/时间在 JPA/ 中处理日期和时间时担心时区差异休眠应用程序?本文探讨了如何在 UTC (GMT) 时区有效存储和检索时态数据,确保跨不同时区进行一致且准确的处理。考虑下面带注释的 JPA 实体:public class Event {...
    编程 发布于2024-11-09
  • 如何使用 CSS 创建动态扩展的文本输入字段?
    如何使用 CSS 创建动态扩展的文本输入字段?
    通过 CSS 增强文本输入响应能力制作 Web 表单时,控制文本输入字段的大小至关重要。 CSS 提供了一种简单的方法来定义其初始尺寸。但是,如果您希望输入随着用户键入而动态扩展并达到最大宽度,该怎么办?本文深入研究了仅 CSS 和基于 HTML 的技术来实现此行为。CSS 和内容可编辑利用 CSS...
    编程 发布于2024-11-09
  • 关于 Javascript Promise 的有趣事实
    关于 Javascript Promise 的有趣事实
    Promise 始终是异步的 Promise 的回调总是在同步代码之后执行 const promise = Promise.resolve(); promise.then(() => console.log('async')); console.log('sync'); ...
    编程 发布于2024-11-09
  • LightFlow:Go 的任务编排框架
    LightFlow:Go 的任务编排框架
    我开发了 LightFlow,一个任务编排框架,旨在简化 Go 中复杂工作流程的管理。它专注于执行时序并减少对外部配置文件的需求。 主要特点: 独立上下文:每个步骤都通过独立上下文链接,仅允许访问相关数据。 可合并流程:您可以灵活组合任务流程,以便在不同流程中重复使用。 检查点恢复:...
    编程 发布于2024-11-09

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3