go学习记录——第七天

Map的声明、初始化和make

这里的map相当于python中的字典,由key:value组成,无序集合。

声明格式如下

1
2
var map1 map[KeyType]ValueType
var map1 map[string]int

在声明时并不知道map长度,map是支持动态增长的。

未初始化的map默认值为nil

key支持使用== or !=进行比较类型,比如string, int, float32。所以数组、切片和结构体不能作为key,含数组、切片的结构体不能作为key,只包含内建类型的struct可以作为key,指针和接口类型可以作为key

如果要用结构体作为key,必须满足以下条件:

  1. 结构体的字段必须是可导出的。
  2. 结构体的字段必须是基本类型。
  3. 结构体不能是指针类型。

value可以是任意类型;通过使用空接口类型,我们可以存储任意类型的数据,但是使用这种类型的值需要先做一个类型断言。

map传递给函数的代价很小,在查找值方面比较快,比线性查找快。但是相比于数组的切片中的索引还是较慢。

map可以用函数做自己的值,这样可以使用分支结构(之前有,第二篇go的学习记录里),key来选择需要执行的函数。

如果key1map1的key,那么就可以使用map1[key1]来获取value,和数组索引是一个样的。

如果key1map1的key,那么就可以使用map1[key1] = value来修改value,和数组索引是一个样的。

同样v := map1[key1]也可以赋值给v,如果key1位置没有值则给值类型的空值。

同样len(map1)也可以获取map的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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"])
}

/* Output:
Map literal at "one" is: 1
Map created at "key2" is: 3.141590
Map assigned at "two" is: 3
Map literal at "ten" is: 0
*/

mapLit说明了 map literals 的使用方法:map可以用{key1 : value1, key2 : value2}的方式初始化。

map 是引用类型的:内存用 make() 来分配。

map 的初始化:make(map[KeyType]ValueType)

简写为 map1 := make(map[keytype]valuetype)

mapAssigned 也是对 mapLit 的引用,对 mapAssigned 的修改会影响 mapLit

绝对不要用 new() 来构造 map,因为使用了会获得一个空引用的指针,相当于声明了一个未初始化的变量并获取了地址。

1
2
3
mapCreated := new(map[string]float32)
//调用 mapCreated["key1"] = 4.5
error:invalid operation: mapCreated["key1"] (index of type *map[string]float32).

map容量

由于 map 是动态增长的,所以其不存在最大限制,但是在初始化时我们仍可以表明初始容量,make(map[keytype]valuetype, cap)

当然在达到容量上限后继续增加,编译器会自动加1,但如果为了性能考虑,我们在已经提前知道大概容量的情况下,可以提前表明。

用切片作为map的值

既然一个key对应一个value,而value又是一个原始类型,那么如果一个key对应一个切片,那么它的value就是一个切片。举个例子,当我们在处理Unix系统上进程时,父进程是key,子进程是value,我们就可以将value定义为[]int类型或其他所有类型的切片,就可与解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
var map1 map[string][]int
map1 = make(map[string][]int)
map1["parent"] = []int{1, 2, 3}
map1["child"] = []int{4, 5, 6}
fmt.Println(map1)
}

/* Output:
map[parent:[1 2 3] child:[4 5 6]]
*/

测试键值对是否存在和删除元素

这里我们可以用 val1, isPresent = map1[key1] 来判断元素是否存在。

isPresent 是一个布尔值,true表示存在,false表示不存在。

如果只想知道这个值存不存在,不关心值是多少,可以这样写 _, ok := map1[key1] // 如果key1存在则ok == true,否则ok为false

或者和 if 混合使用,if _, ok := map1[key1]; ok { // ... }
map1 中删除元素,可以这样写 delete(map1, key1),该操作即使 value1 已经不存在也不会报错。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main
import "fmt"

func main() {
var value int
var isPresent bool

map1 := make(map[string]int)
map1["New Delhi"] = 55
map1["Beijing"] = 20
map1["Washington"] = 25
value, isPresent = map1["Beijing"]
if isPresent {
fmt.Printf("The value of \"Beijing\" in map1 is: %d\n", value)
} else {
fmt.Printf("map1 does not contain Beijing")
}

value, isPresent = map1["Paris"]
fmt.Printf("Is \"Paris\" in map1 ?: %t\n", isPresent)
fmt.Printf("Value is: %d\n", value)

// delete an item:
delete(map1, "Washington")
value, isPresent = map1["Washington"]
if isPresent {
fmt.Printf("The value of \"Washington\" in map1 is: %d\n", value)
} else {
fmt.Println("map1 does not contain Washington")
}
}
/*output
The value of "Beijing" in map1 is: 20
Is "Paris" in map1 ?: false
Value is: 0
map1 does not contain Washington
*/

for-range用法

可以用 for 来读取 map

1
2
3
for key, value := range map1 {
...
}

第一个返回值是key,第二个返回值是value。这里需要注意的是返回的均为在循环内可用的局部变量,key为可选值

1
2
3
for _, value := range map1 {
...
}

如果只想获得key,可以这样写

1
2
3
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"

func main() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}
}
/*output
key is: 3 - value is: 3.000000
key is: 1 - value is: 1.000000
key is: 4 - value is: 4.000000
key is: 2 - value is: 2.000000
*/

这里可以看到输出并不是按照顺序的,也就是说不是按照key or value进行排列。map 和数据结构中的hash,python中的dict一样,拿来计数就可以了,如果为了规则索引还是去用slice吧。(这里不免想到leetcode上很多题需要用一个hash或者dict来计数,那go在做题的时候直接用map就好咯)

map类型的切片

假设要获取一个 map 类型的切片,我们必须使用两次 make() 函数,第一次分配切片,第二次分配切片中的每个 map 元素。

实例(英文注释是原本的,中文注释是我使用AI去写的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main
import "fmt"

// main函数是程序的入口,演示了两种不同的方式来初始化和修改切片中的映射
func main() {
// Version A:
// 创建一个包含5个映射的切片,并初始化每个映射
items := make([]map[int]int, 5)
for i:= range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)

// Version B: NOT GOOD!
// 创建一个包含5个映射的切片,尝试初始化每个映射,但这种方式是错误的
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}
/*ooutput
Version A: Value of items: [map[1:2] map[1:2] map[1:2] map[1:2] map[1:2]]
Version B: Value of items: [map[] map[] map[] map[] map[]]
*/

这里需要注意的是,A中所得到的是切片,而B中的仅仅是对切片目标元素进行了拷贝,并没有进行初始化。

map的排序

按照 map 的概念来看,map 是无序的,所以当需要对它进行排序时我们需要将key(或value)拷贝到一个切片,再对切片进行排序(使用 sort 包,前一篇blog有)

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// the telephone alphabet:
package main
import (
"fmt"
"sort"
)

var (
barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
"delta": 87, "echo": 56, "foxtrot": 12,
"golf": 34, "hotel": 16, "indio": 87,
"juliet": 65, "kili": 43, "lima": 98}
)

func main() {
fmt.Println("unsorted:")
for k, v := range barVal {
fmt.Printf("Key: %v, Value: %v / ", k, v)
}
keys := make([]string, len(barVal))
i := 0
for k, _ := range barVal {
keys[i] = k
i++
}
sort.Strings(keys)
fmt.Println()
fmt.Println("sorted:")
for _, k := range keys {
fmt.Printf("Key: %v, Value: %v / ", k, barVal[k])
}
}
/*output
unsorted:
Key: bravo, Value: 56 / Key: echo, Value: 56 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: alpha, Value: 34 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: kili, Value: 43 / Key: lima, Value: 98 /
sorted:
Key: alpha, Value: 34 / Key: bravo, Value: 56 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: echo, Value: 56 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: kili, Value: 43 / Key: lima, Value: 98 /
*/

如果想要一个排序的列表,那么最好用结构体切片。

1
2
3
4
type name struct {
key string
value int
}

将map的键值对调

这里直接通过 for-range 循环遍历原始映射(map),并在循环中将原本的值设置为新的键,将原本的键设置为新的值,从而实现键值对的调换。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
)

func main() {
// 原始映射
original := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}

// 创建一个新的映射来存储调换后的键值对
swapped := make(map[int]string)

// 遍历原始映射并调换键值
for key, value := range original {
swapped[value] = key // 将原本的值作为新键,原本的键作为新值
}

// 输出调换后的映射
fmt.Println("原始映射:", original)
fmt.Println("调换后的映射:", swapped)
}

go学习记录——第七天
https://www.lx02918.ltd/2024/08/15/go-study-seventh-day/
作者
Seth
发布于
2024年8月15日
许可协议