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

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

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

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]删除
最新教程 更多>
  • 在Pandas中如何将年份和季度列合并为一个周期列?
    在Pandas中如何将年份和季度列合并为一个周期列?
    pandas data frame thing commans date lay neal and pree pree'和pree pree pree”,季度 2000 q2 这个目标是通过组合“年度”和“季度”列来创建一个新列,以获取以下结果: 在Python中,可以直接使用“...
    编程 发布于2025-05-09
  • 在Java中如何为PNG文件添加坐标轴和标签?
    在Java中如何为PNG文件添加坐标轴和标签?
    如何用java 在现有png映像中添加轴和标签的axes和labels如何注释png文件可能具有挑战性。与其尝试可能导致错误和不一致的修改,不如建议在图表创建过程中集成注释。使用JFReechArt import java.awt.color; 导入java.awt.eventqueue; 导入...
    编程 发布于2025-05-09
  • 找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    如何在mySQL中使用mySql 检索最大计数,您可能会遇到一个问题,您可能会在尝试使用以下命令:理解错误正确找到由名称列分组的值的最大计数,请使用以下修改后的查询: 计数(*)为c 来自EMP1 按名称组 c desc订购 限制1 查询说明 select语句提取名称列和每个名称...
    编程 发布于2025-05-09
  • 反射动态实现Go接口用于RPC方法探索
    反射动态实现Go接口用于RPC方法探索
    在GO 使用反射来实现定义RPC式方法的界面。例如,考虑一个接口,例如:键入myService接口{ 登录(用户名,密码字符串)(sessionId int,错误错误) helloworld(sessionid int)(hi String,错误错误) } 替代方案而不是依靠反射...
    编程 发布于2025-05-09
  • 在细胞编辑后,如何维护自定义的JTable细胞渲染?
    在细胞编辑后,如何维护自定义的JTable细胞渲染?
    在JTable中维护jtable单元格渲染后,在JTable中,在JTable中实现自定义单元格渲染和编辑功能可以增强用户体验。但是,至关重要的是要确保即使在编辑操作后也保留所需的格式。在设置用于格式化“价格”列的“价格”列,用户遇到的数字格式丢失的“价格”列的“价格”之后,问题在设置自定义单元格...
    编程 发布于2025-05-09
  • 如何将多种用户类型(学生,老师和管理员)重定向到Firebase应用中的各自活动?
    如何将多种用户类型(学生,老师和管理员)重定向到Firebase应用中的各自活动?
    Red: How to Redirect Multiple User Types to Respective ActivitiesUnderstanding the ProblemIn a Firebase-based voting app with three distinct user type...
    编程 发布于2025-05-09
  • 如何使用“ JSON”软件包解析JSON阵列?
    如何使用“ JSON”软件包解析JSON阵列?
    parsing JSON与JSON软件包 QUALDALS:考虑以下go代码:字符串 } func main(){ datajson:=`[“ 1”,“ 2”,“ 3”]`` arr:= jsontype {} 摘要:= = json.unmarshal([] byte(...
    编程 发布于2025-05-09
  • 在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8表中将latin1字符转换为utf8 ,您遇到了一个问题,其中含义的字符(例如,“jáuòiñe”)在utf8 table tabled tablesset中被extect(例如,“致电。为了解决此问题,您正在尝试使用“ mb_convert_encoding”和“ iconv”转换受...
    编程 发布于2025-05-09
  • Go web应用何时关闭数据库连接?
    Go web应用何时关闭数据库连接?
    在GO Web Applications中管理数据库连接很少,考虑以下简化的web应用程序代码:出现的问题:何时应在DB连接上调用Close()方法?,该特定方案将自动关闭程序时,该程序将在EXITS EXITS EXITS出现时自动关闭。但是,其他考虑因素可能保证手动处理。选项1:隐式关闭终止数...
    编程 发布于2025-05-09
  • 在程序退出之前,我需要在C ++中明确删除堆的堆分配吗?
    在程序退出之前,我需要在C ++中明确删除堆的堆分配吗?
    在C中的显式删除 在C中的动态内存分配时,开发人员通常会想知道是否有必要在heap-procal extrable exit exit上进行手动调用“ delete”操作员,但开发人员通常会想知道是否需要手动调用“ delete”操作员。本文深入研究了这个主题。 在C主函数中,使用了动态分配变量(H...
    编程 发布于2025-05-09
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-05-09
  • 如何在Chrome中居中选择框文本?
    如何在Chrome中居中选择框文本?
    选择框的文本对齐:局部chrome-inly-ly-ly-lyly solument 您可能希望将文本中心集中在选择框中,以获取优化的原因或提高可访问性。但是,在CSS中的选择元素中手动添加一个文本 - 对属性可能无法正常工作。初始尝试 state)</option> < op...
    编程 发布于2025-05-09
  • Java中假唤醒真的会发生吗?
    Java中假唤醒真的会发生吗?
    在Java同步中伪装唤醒的概念已经是讨论的主题。尽管存在这种行为的潜力,但问题仍然存在:它们实际上是在实践中发生的吗? Linux的唤醒机制根据Wikipedia关于伪造唤醒的文章,linux实现了pthread_cond_wait()功能的Linux实现,利用了Futex System Call...
    编程 发布于2025-05-09
  • 解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    Resolving Incorrect String Value Exception When Inserting EmojiWhen attempting to insert a string containing emoji characters into a MySQL database us...
    编程 发布于2025-05-09
  • 如何正确使用与PDO参数的查询一样?
    如何正确使用与PDO参数的查询一样?
    在pdo 中使用类似QUERIES在PDO中的Queries时,您可能会遇到类似疑问中描述的问题:此查询也可能不会返回结果,即使$ var1和$ var2包含有效的搜索词。错误在于不正确包含%符号。通过将变量包含在$ params数组中的%符号中,您确保将%字符正确替换到查询中。没有此修改,PDO...
    编程 发布于2025-05-09

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

Copyright© 2022 湘ICP备2022001581号-3