数组、切片、map
一组同类型的值,Go 里常用三种容器:数组长度写死,切片长度可变,map 按 key 存 value。日常写代码几乎都用切片和 map。
数组
数组里的元素必须同一类型,声明时长度是类型的一部分。[3]int 和 [4]int 不是同一种类型。
var nums [3]int
nums[0] = 1
nums[1] = 2
fmt.Println(nums) // [1 2 0]
go
也可以在声明时写满:
names := [3]string{"槿", "go", "笔记"}
go
长度用 ... 让编译器数:
names := [...]string{"a", "b", "c"} // [3]string
go
改某个位置只能靠下标。数组是值类型,赋值或传参会整份拷贝。
索引
下标从 0 开始,最后一个是 len(a)-1。越界会直接 panic。
a := [3]int{10, 20, 30}
fmt.Println(a[0], a[2]) // 10 30
go
切片
数组长度写死后就不能再长,所以更常用切片(slice)。切片长度可变,底层还是一段数组。
var s []int // nil 切片
s = append(s, 1, 2)
fmt.Println(s, len(s), cap(s))
go
字面量直接写:
s := []int{1, 2, 3}
go
len 是当前元素个数,cap 是底层数组还能装多少。append 空间不够时会换一块更大的数组。
make 函数
除了基本类型,只声明不赋值的切片、map、channel 是 nil。nil 切片可以 append,但读下标会 panic。要用指定长度、容量时用 make:
s := make([]int, 3, 5) // len=3, cap=5,三个元素都是 0
s = append(s, 4)
go
make([]T, n) 只写长度时,容量等于长度。
为什么叫切片
因为它常常是从数组(或另一个切片)上「切」出来的:
a := [5]int{0, 1, 2, 3, 4}
s := a[1:4] // [1 2 3],含头不含尾
go
切片和底层数组共享存储。改 s[0],a[1] 也会变。需要独立副本时用 append 到新切片,或 copy。
dst := make([]int, len(s))
copy(dst, s)
go
切片排序
整数、浮点、字符串用 slices(Go 1.21+)或旧的 sort:
import "slices"
s := []int{3, 1, 2}
slices.Sort(s) // [1 2 3]
go
自定义顺序用 slices.SortFunc。
map
map 是无序的键值对。key 必须可比较(整数、字符串、布尔等),value 任意类型。用之前必须初始化,否则赋值会 panic。
var m map[string]int // nil,不能写
m = make(map[string]int)
m["age"] = 18
go
也可以字面量一起写:
m := map[string]int{
"age": 18,
"n": 2,
}
go
删 key 用 delete(m, "age")。遍历顺序每次不一定相同。
key 其实不限于「基本类型」,结构体、数组只要可比较也能当 key;切片、map、函数不行。
map 取值
一个返回值时,没有这个 key 就得到 value 的零值,分不清「没有」和「值就是 0」:
age := m["age"]
go
两个返回值时,第二个是「在不在」:
age, ok := m["age"]
if !ok {
fmt.Println("没有 age")
}
go