容器用法
go 有三种一等公民类型
- 数组
- 切片
- 映射
映射的键必须是可比较类型,数组和切片的键值类型均为内置类型int。
数组仅由一个直接部分组成,切片或映射由一个直接部分和一个引用的间接部分组成
一个赋值结束后,源值和目标值可能会共享底层间接部分。
容器类型的字面量
- 数组:
[N]T - 切片:
[]T - 映射:
map[K]T
[4]bool{false, true, true, false}
[]string{"break", "continue", "fallthrough"}
map[string]int{"C": 1972, "Python": 1991, "Go": 2009}
容器类型的零值表示形式
- 数组:
[100]int{} - 切片: nil
- 映射: nil
函数、通道和接口类型的零值也用nil来表示。
数组在声明时内存空间就被开辟出来,但 nil 的切片或映射空间尚未开辟。
[]T{}与[]T(nil)不等价,同样,map[K]T{}和map[K]T(nil)也不等价。
容器值的比较
映射和切片类型都不可比较。 可以和 nil 比较。 大多数数组都是可比较的,但元素类型为不可比较类型的数组不可比较。
容器赋值
- 映射和切片:会共享底层的元素
- 数组:不会共享,每次赋值将所有元素从源数组复制到目标数组。
m0 := map[int]int{0:7, 1:8, 2:9}
m1 := m0
m1[0] = 2
使用make函数来创建切片和映射
// map
make(M, n)
make(M)
//slice
make(S, length, capacity)
make(S, length)
容器遍历
遍历一个nil映射或nil切片是允许的,相当于一个空操作
添加或删除容器的元素
s = append(s, elements...) // 插入到结尾
s = append(elements, s...) // 插入到开头
s, e = s[1:], s[0] // pop front, 弹出第一个元素
s, e = s[:len(s)-1], s[len(s)-1] // pop back
s = append([]T{e}, s...) // push front
s = append(s, e) // push back
删除满足某条件的切片元素
result := s[:0] // result 与 s 共享底层数组
for _, v := range s {
if keep(v) {
result = append(result, v)
}
}
if clear {
temp := s[len(result):]
for i := range temp {
temp[i] = t0 // t0 是 T 的零值
}
}
return result
Go 1.21 引入了 clear内置函数,可以清空 map 或 slice
s := []int{1, 2, 3}
clear(s)
容器元素的可寻址性
- 一个切片值的任何元素都是可寻址的
- 任何映射的元素都是不可寻址的
type T struct{age int}
mt := map[string]T{}
mt["John"] = T{age: 29} // 整体修改是允许的
mt["John"].age = 30 // error,这有点反直觉,这个限制以后可能会被移除
t : = mt["John"]
t.age = 30 // 这是允许的
并发安全
- 并发读是安全的
- 并发读写未进行同步,是不安全的
内存泄露
如果删除了切片某些元素,应将删除元素的槽位赋类型的零值,以避免内存泄露
temp := s[len(s): len(s)
容器的实现
切片的实现
type _slice struct {
elements unsafe.Pointer
len int
cap int
}
切片可能位于一个比较大的内存块上,但切片只感知到 0~cap的内存子片段。
如果使用 append 向切片添加元素,新生成的切片可能与基础切片相同,也可能不同。
- 如果添加元素数量没有超过 cap,仍然在原来的基础切片的内存片段。
- 如果超过了 cap,新的底层内存会被开辟。