介绍
欢迎 Gophers 来到这个新章节。
在本章节中,我们将学习 map 的基本用法,并分析 Go 中 nil
的存在。
完成本章节后,你将能够在日常开发任务中应用 map。
知识点:
- 声明一个 map
- 初始化一个 map
- 添加、删除、更新和查找 map 元素
- 检查 map 元素是否存在
- 遍历 map
欢迎 Gophers 来到这个新章节。
在本章节中,我们将学习 map 的基本用法,并分析 Go 中 nil
的存在。
完成本章节后,你将能够在日常开发任务中应用 map。
知识点:
那么,什么是字典?
在计算机科学中,字典是一种特殊的数据结构,由键值对组成。
键值对:一对元素,其中一个元素称为键(key),另一个称为值(value)。
那么,为什么我们需要使用字典?
因为字典在添加、删除、更新和查找元素等操作上具有很高的效率,我相信你会发现这种数据结构非常有用。
对于一种新的数据结构,第一步是学习如何声明它。
对于 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
关键字声明一个字典。
在上一节中,我们演示了如何使用 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]
手动初始化的优点:
make
关键字。既然我们可以初始化一个空字典,那么我们也可以为它赋予一些初始值。
在 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
的特性