Go 字典基础

GolangGolangBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

欢迎 Gophers 来到这个新章节。

在本章节中,我们将学习 map 的基本用法,并分析 Go 中 nil 的存在。

完成本章节后,你将能够在日常开发任务中应用 map。

知识点:

  • 声明一个 map
  • 初始化一个 map
  • 添加、删除、更新和查找 map 元素
  • 检查 map 元素是否存在
  • 遍历 map

字典简介

那么,什么是字典?

在计算机科学中,字典是一种特殊的数据结构,由键值对组成。

键值对:一对元素,其中一个元素称为键(key),另一个称为值(value)。

那么,为什么我们需要使用字典?

因为字典在添加、删除、更新和查找元素等操作上具有很高的效率,我相信你会发现这种数据结构非常有用。

图片描述

声明 Map

对于一种新的数据结构,第一步是学习如何声明它。

对于 map,定义它的方式是:

var variableName map[keyType]valueType

例如:

var a map[string]int

然而,由于 map 是引用类型,如果我们像上面那样声明它,就会遇到一个问题。

让我们在 ~/project 目录下创建一个 map.go 文件:

touch ~/project/map.go

map.go 中编写以下代码:

package main

func main() {
    // 声明一个名为 m 的 map,键类型为 string,值类型为 int
    var m map[string]int
    // 向其中添加一个数据项 "labex"->1
    m["labex"] = 1 // 程序崩溃
}

运行程序:

go run map.go

我们发现程序崩溃并抛出了一个错误:

panic: assignment to entry in nil map

这意味着向一个 nil map 赋值是一个错误的行为。

这是因为,对于像切片、map、通道和指针这样的数据结构,它们在使用前必须被初始化。

nil 是 map 的初始默认值,这意味着在定义时没有为变量分配内存。

初始值 nil

在上一节中,我们提到向一个 nil map 赋值是一个错误的行为。

让我们借此机会探讨一下 Go 中 nil 的真正含义。

nil 的本质是一个预声明的标识符。

对于基本数据类型,它们的初始值是不同的:

  • 布尔值
  • 数值
  • 字符串

然而,对于切片、字典、指针、通道和函数,它们的初始值是 nil。这并不是我们所熟悉的初始默认值。

这体现在被赋值为 nil 的实例化对象上。虽然它们可以被打印出来,但却无法使用。

此外,关于 nil 还有一些需要注意的地方。

不同类型 nil 的不可比性

package main

import "fmt"

func main() {
    var m map[int]string
    var p *int
    fmt.Printf("%v", m == p)
}

程序输出如下:

invalid operation: m == p (mismatched types map[int]string and *int)

也就是说,我们不能将 int 类型指针的 nil 与 map 的 nil 进行比较。

它们是不可比较的。

nil 不是关键字

package main

import "fmt"

func main() {
    var nilValue = "= =$"
    fmt.Println(nilValue)
}

程序输出如下:

= =$

我们可以定义一个名为 nil 的变量,并且它可以正常编译。然而,我们强烈建议你在实际开发中不要这样做。

nil 的不可比性

package main

import "fmt"

func main() {
    fmt.Println(nil == nil)
}

程序输出如下:

invalid operation: nil == nil (operator == not defined on nil)

使用 make 关键字声明

在理解了 nil 的含义后,让我们来解决字典的内存分配问题。

这里我们使用 make 关键字来分配内存。

make 函数会生成一个指定类型的值,并将其初始化为返回值。

package main

import "fmt"

func main() {
    var m map[string]int     // 声明一个字典
    m = make(map[string]int) // 为字典分配内存
    m["labex"] = 1       // 向字典中添加数据
    fmt.Println(m)
}

程序输出如下:

map[labex:1]

以上代码展示了如何使用 make 关键字为已声明的字典分配内存。

我们还可以简化这一过程:

map.go 中编写以下代码:

package main

import "fmt"

func main() {
    m := make(map[string]int) // 声明并初始化一个字典
    m["labex"] = 666      // 向字典中添加数据
    fmt.Println(m)
}

程序输出如下:

map[labex:666]

以上代码展示了如何使用 make 关键字声明一个字典。

手动初始化空 Map

在上一节中,我们演示了如何使用 make 关键字初始化一个 map。现在,让我们探索另一种方法:使用字面量语法手动初始化一个空 map。

这种方法简洁明了,允许你创建一个可以直接使用的空 map,而无需显式分配内存。

map.go 中编写以下代码:

package main

import "fmt"

func main() {
    // 使用字面量语法初始化一个空 map
    m := map[string]int{}

    // 向 map 中添加元素
    m["labex"] = 777
    m["labs"] = 11

    fmt.Println(m) // 输出添加元素后的 map
}

语法 map[keyType]valueType{} 会创建一个可以直接使用的空 map。初始化后,你可以使用 map[key] = value 语法向 map 中添加元素。

当你运行上述代码时,程序将输出:

map[labex:777 labs:11]

手动初始化的优点:

  • 提供了一种快速创建空 map 的方法,而无需使用 make 关键字。
  • 当你需要从一个空 map 开始并动态填充它时非常有用。

实际初始化字典

既然我们可以初始化一个空字典,那么我们也可以为它赋予一些初始值。

map.go 中编写以下代码:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
    }
    m["labby"] = 666
    fmt.Println(m)
}

程序输出如下:

map[labby:666 labs:11 labex:777]

请注意,在 Go 中初始化字典时,必须在每个元素后添加逗号,包括最后一个元素。

添加和更新字典元素

向字典中添加元素非常简单,正如上面的代码示例所示。

语法如下:

dictionaryInstance[keyToAdd] = valueToAdd

例如:

m := map[string]int{}
m["labby"] = 666

注意:在 Go 中,map 中的每个 key 必须是唯一的。

map.go 中编写以下代码:

package main

import "fmt"

func main() {
    m := map[string]int{}
    m["labby"] = 666
    m["labby"] = 777
    fmt.Println(m)
}

程序输出如下:

map[labby:777]

我们发现输出的值被更改为 777。也就是说,如果我们向同一个 key 写入不同的值,对应的 value 将会更新为新的值。

这就是我们更新或修改字典的方式。

删除字典元素

我们如何从字典中删除一个元素?

我们需要使用内置的 delete 函数。

map.go 中编写以下代码:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
        "labby": 666,
    }
    fmt.Println(m)
    delete(m, "labs")
    fmt.Println(m)
}

程序输出如下:

map[labby:666 labs:11 labex:777]
map[labby:666 labex:777]

delete 函数接受两个参数。第一个参数是要操作的字典,第二个参数是要删除的键。

当然,delete 函数还有其他用途,但在本实验中我们只讨论它在字典中的用法。

查找字典元素

当我们查找一个不存在的字典元素时会发生什么?

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
}

程序输出如下:

The value of labs is: 0

我们发现,如果元素在字典中不存在,查询它会返回值类型的默认值。

那么字典中值为 0 的键呢?

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
    fmt.Print("The value of labex is: ")
    fmt.Println(m["labex"])
}

程序输出如下:

The value of labs is: 0
The value of labex is: 0

我们发现,对于字典来说,一个不存在的值和一个存在但值为默认值的表现形式是相同的。

这给我们带来了很多困惑。我们该如何解决这个问题呢?

事实证明,Go 的开发者已经考虑到了这个问题。当我们查询字典中的一个元素时,会有一个或两个变量返回值。

即:

labs, ok := m["labs"]

修改 map.go

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    labs, ok := m["labs"]
    fmt.Print("The value of labs is: ")
    fmt.Print(labs)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok)
    labex, ok2 := m["labex"]
    fmt.Print("The value of labex is: ")
    fmt.Print(labex)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok2)
}

程序输出如下:

The value of labs is: 0 Does it exist? false
The value of labex is: 0 Does it exist? true

现在,我们可以通过查询的第二个返回值来判断返回的结果是存在的初始默认值还是不存在的初始默认值。

遍历字典

在某些场景中,我们需要遍历整个字典,查询每个键值对并对其进行处理。我们该如何实现这一点呢?

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "labex": 777,
        "labs":   11,
        "labby":     666,
    }
    for key, value := range m {
        fmt.Println(key, value)
    }
}

程序输出如下:

labby 666
labs 11
labex 777

从输出中可以看出,遍历字典的方式与遍历切片或数组非常相似。

总结

在本实验中,我们学习了字典的基本用法,包括:

  • 字典的声明以及如何解决声明问题
  • 初始值 nil 的特性
  • 初始化字典的方法
  • 在字典中添加、删除、更新和查找元素
  • 检测字典元素的存在性
  • 遍历字典

您可能感兴趣的其他 Golang 教程