Files
the-way-to-go_ZH_CN/eBook/08.1.md
Haigang Zhou 30ca13a369 7-8 章修改 (#842)
Co-authored-by: Joe Chen <jc@unknwon.io>
2022-05-10 13:53:30 +08:00

147 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 8.1 声明、初始化和 make
## 8.1.1 概念
`map` 是引用类型,可以使用如下声明:
```go
var map1 map[keytype]valuetype
var map1 map[string]int
```
`[keytype]``valuetype` 之间允许有空格,但是 gofmt 移除了空格)
在声明的时候不需要知道 `map` 的长度,`map` 是可以动态增长的。
未初始化的 `map` 的值是 `nil`
key 可以是任意可以用 `==` 或者 `!=` 操作符比较的类型,比如 `string``int``float32(64)`。所以数组、切片和结构体不能作为 key (译者注:含有数组切片的结构体不能作为 key只包含内建类型的 `struct` 是可以作为 key 的),但是指针和接口类型可以。如果要用结构体作为 key 可以提供 `Key()``Hash()` 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key。
value 可以是任意类型的;通过使用空接口类型(详见[第 11.9 节](11.9.md)),我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言(详见[第 11.3 节](11.3.md))。
`map` 传递给函数的代价很小:在 32 位机器上占 4 个字节64 位机器上占 8 个字节,无论实际上存储了多少数据。通过 key 在 `map` 中寻找值是很快的,比线性查找快得多,但是仍然比从数组和切片的索引中直接读取要慢 100 倍;所以如果你很在乎性能的话还是建议用切片来解决问题。
`map` 也可以用函数作为自己的值,这样就可以用来做分支结构(详见[第 5 章](05.0.md)key 用来选择要执行的函数。
如果 `key1``map1` 的 key那么 `map1[key1]` 就是对应 `key1` 的值,就如同数组索引符号一样(数组可以视为一种简单形式的 `map`key 是从 0 开始的整数)。
`key1` 对应的值可以通过赋值符号来设置为 val1`map1[key1] = val1`
`v := map1[key1]` 可以将 `key1` 对应的值赋值给 `v`;如果 `map` 中没有 `key1` 存在,那么 `v` 将被赋值为 `map1` 的值类型的空值。
常用的 `len(map1)` 方法可以获得 `map` 中的 pair 数目,这个数目是可以伸缩的,因为 map-pairs 在运行时可以动态添加和删除。
示例 8.1 [make_maps.go](examples/chapter_8/make_maps.go)
```go
package main
import "fmt"
func main() {
var mapLit map[string]int
//var mapCreated map[string]float32
var mapAssigned map[string]int
mapLit = map[string]int{"one": 1, "two": 2}
mapCreated := make(map[string]float32)
mapAssigned = mapLit
mapCreated["key1"] = 4.5
mapCreated["key2"] = 3.14159
mapAssigned["two"] = 3
fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
}
```
输出结果:
Map literal at "one" is: 1
Map created at "key2" is: 3.141590
Map assigned at "two" is: 3
Mpa literal at "ten" is: 0
`mapLit` 说明了 `map literals` 的使用方法: `map` 可以用 `{key1: val1, key2: val2}` 的描述方法来初始化,就像数组和结构体一样。
`map`**引用类型** 的: 内存用 `make()` 方法来分配。
`map` 的初始化:`var map1 = make(map[keytype]valuetype)`
或者简写为:`map1 := make(map[keytype]valuetype)`
上面例子中的 `mapCreated` 就是用这种方式创建的:`mapCreated := make(map[string]float32)`
相当于:`mapCreated := map[string]float32{}`
`mapAssigned` 也是 `mapLit` 的引用,对 `mapAssigned` 的修改也会影响到 `mapLit` 的值。
**不要使用 `new()`,永远用 `make()` 来构造 `map`**
**注意** 如果你错误地使用 `new()` 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:
```go
mapCreated := new(map[string]float32)
```
接下来当我们调用:`mapCreated["key1"] = 4.5` 的时候,编译器会报错:
invalid operation: mapCreated["key1"] (index of type *map[string]float32).
为了说明值可以是任意类型的,这里给出了一个使用 `func() int` 作为值的 `map`
示例 8.2 [map_func.go](examples/chapter_8/map_func.go)
```go
package main
import "fmt"
func main() {
mf := map[int]func() int{
1: func() int { return 10 },
2: func() int { return 20 },
5: func() int { return 50 },
}
fmt.Println(mf)
}
```
输出结果为:`map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0]`: 整型都被映射到函数地址。
## 8.1.2 map 容量
和数组不同,`map` 可以根据新增的 key-value 对动态的伸缩,因此它不存在固定长度或者最大限制。但是你也可以选择标明 `map` 的初始容量 `capacity`,就像这样:`make(map[keytype]valuetype, cap)`。例如:
```go
map2 := make(map[string]float32, 100)
```
`map` 增长到容量上限的时候,如果再增加新的 key-value 对,`map` 的大小会自动加 1。所以出于性能的考虑对于大的 `map` 或者会快速扩张的 `map`,即使只是大概知道容量,也最好先标明。
这里有一个 `map` 的具体例子,即将音阶和对应的音频映射起来:
```go
noteFrequency := map[string]float32 {
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87, "A4": 440}
```
## 8.1.3 用切片作为 map 的值
既然一个 key 只能对应一个 value而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理 Unix 机器上的所有进程以父进程pid 为整型)作为 key所有的子进程以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 `[]int` 类型或者其他类型的切片,就可以优雅地解决这个问题。
这里有一些定义这种 `map` 的例子:
```go
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
```
## 链接
- [目录](directory.md)
- 上一节:[Map](08.0.md)
- 下一节:[测试键值对是否存在及删除元素](08.2.md)