这篇笔记梳理 Go 语言三种内置容器的核心用法:数组、切片、映射。每种都覆盖定义、增删改查、遍历和常见坑点。
📦 一、数组(Array)
关键词: 固定长度 · 值类型 · 编译期确定大小
Go 中数组的长度是类型的一部分 —— [3]int 和 [4]int 是两种不同的类型。
1.1 四种初始化方式
1 2 3 4 5 6 7 8 9 10 11 12 13
| var a [3]int a[0] = 1
b := [3]int{1, 2, 3}
c := [...]int{1, 2, 3, 4, 5}
d := [5]int{0: 1, 3: 4}
|
1.2 访问与修改
1 2 3 4 5 6 7
| arr := [5]int{10, 20, 30, 40, 50}
fmt.Println(arr[0]) fmt.Println(arr[4])
arr[2] = 99 ← 修改 fmt.Println(arr)
|
⚠️ 索引越界:常量索引编译期报错,变量索引运行期 panic。
1.3 遍历
1 2 3 4 5 6 7 8 9 10 11 12
| arr := [4]string{"Go", "Java", "Python", "Rust"}
for i, v := range arr { fmt.Printf("[%d] %s\n", i, v) }
for i := range arr { ... }
for _, v := range arr { ... }
|
1.4 多维数组
1 2 3 4 5 6 7 8 9 10 11
| var matrix [2][3]int matrix[0] = [3]int{1, 2, 3} matrix[1] = [3]int{4, 5, 6}
for i := range matrix { for j := range matrix[i] { fmt.Printf("[%d][%d]=%d ", i, j, matrix[i][j]) } }
|
1.5 ⚠️ 数组是值类型
赋值和传参时完整拷贝整个数组:
1 2 3 4 5 6
| a := [3]int{1, 2, 3} b := a b[0] = 99
fmt.Println(a) fmt.Println(b)
|
踩坑: 大数组传参会拷贝整个数组,开销很大 → 改用切片。
✂️ 二、切片(Slice)
关键词: 动态长度 · 引用类型 · 最常用容器
切片不存储数据,而是指向底层数组的一段连续区域。
2.1 四种初始化方式
1 2 3 4 5 6 7 8 9 10 11 12 13
| s1 := make([]int, 5) s2 := make([]int, 3, 10)
s3 := []int{1, 2, 3, 4, 5}
arr := [5]int{10, 20, 30, 40, 50} s4 := arr[1:4]
var s5 []int
|
2.2 len vs cap
| 含义 | 示例 |
|---|
| len | 当前有多少个元素 | len([0,0,0]) = 3 |
| cap | 不触发扩容最多能装多少 | 从切片起点到底层数组末尾 |
1 2 3
| s := make([]int, 3, 10)
|
2.3 追加元素 — append
1 2 3 4 5
| s := []int{1, 2, 3}
s = append(s, 4) s = append(s, 5, 6, 7) s = append(s, other...)
|
⚠️ 关键:必须把返回值赋回原变量 s = append(s, ...),append 可能返回新地址。
2.4 扩容机制
当 len == cap 时再 append,Go 会分配更大的底层数组(通常翻倍):
1 2 3 4 5
| s := make([]int, 0, 2)
s = append(s, 1) s = append(s, 2) s = append(s, 3)
|
2.5 删除元素
Go 没有内置删除函数,用切片拼接实现:
1 2 3 4 5 6 7 8 9 10
| s := []int{1, 2, 3, 4, 5}
s = append(s[:2], s[3:]...)
s = s[1:]
s = s[:len(s)-1]
|
2.6 遍历
1 2 3 4 5
| s := []string{"Go", "Java", "Python"}
for i, v := range s { fmt.Printf("[%d] %s\n", i, v) }
|
2.7 copy — 安全复制
1 2 3 4 5
| src := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src)) copy(dst, src)
|
2.8 ⚠️ 切片是引用类型
共享底层数组,修改一个会影响另一个:
1 2 3 4 5
| a := []int{1, 2, 3} b := a b[0] = 99
fmt.Println(a)
|
1 2 3 4 5
| arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] s[0] = 99
fmt.Println(arr)
|
想要独立副本?用 copy:
1 2 3 4 5 6
| s := arr[1:4] indep := make([]int, len(s)) copy(indep, s) indep[0] = 99
fmt.Println(arr)
|
2.9 nil 切片 vs 空切片
| nil 切片 | 空切片 |
|---|
| 写法 | var s []int | []int{} 或 make([]int, 0) |
== nil | true | false |
| JSON 序列化 | null | [] |
| 能否 range | ✅ | ✅ |
| 能否 append | ✅ | ✅ |
💡 一般用 var s []int 就行,append 时 Go 会自动分配底层数组。
🗂️ 三、映射(Map)
关键词: 键值对 · 哈希表 · O(1) 查找
键必须是可比较的类型(切片、map、函数不能作 key)。
3.1 三种初始化方式
1 2 3 4 5 6 7 8 9 10 11 12
| m1 := make(map[string]int)
m2 := map[string]int{ "Go": 1, "Java": 2, "Python": 3, }
var m3 map[string]int
|
3.2 添加与修改
1 2 3 4 5 6
| m := make(map[string]int)
m["Go"] = 1 m["Java"] = 2
m["Go"] = 99
|
⚠️ nil map 写入会 panic! 必须先 make。
1 2 3 4 5
| var m map[string]int m["key"] = 1
m = make(map[string]int) m["key"] = 1
|
3.3 查找 — comma-ok 惯用法
1 2 3 4 5 6 7 8 9 10 11 12 13
| m := map[string]int{"Go": 1, "Java": 2}
v := m["Rust"]
if val, ok := m["Go"]; ok { fmt.Println("找到:", val) }
if _, ok := m["Rust"]; !ok { fmt.Println("不存在") }
|
3.4 删除
1 2 3 4
| m := map[string]int{"Go": 1, "Java": 2, "Python": 3}
delete(m, "Java") delete(m, "Rust")
|
3.5 遍历(无序!)
1 2 3 4 5
| m := map[string]int{"Go": 1, "Java": 2, "Python": 3}
for key, val := range m { fmt.Printf("%s => %d\n", key, val) }
|
⚠️ map 遍历是随机的! 每次运行顺序可能不同。
需要有序遍历?先排序 key:
1 2 3 4 5 6 7 8 9
| keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys)
for _, k := range keys { fmt.Printf("%s => %d\n", k, m[k]) }
|
3.6 map 是引用类型
1 2 3 4 5
| m1 := map[string]int{"Go": 1} m2 := m1 m2["Go"] = 99
fmt.Println(m1)
|
3.7 key 类型限制
| ✅ 可以 | ❌ 不可以 |
|---|
int, string, bool | 切片 []int |
数组 [2]int | map map[string]int |
| 指针 | 函数 func() |
3.8 操作速查
1 2 3 4 5 6 7
| m := make(map[string]int)
m["key"] = 1 val, ok := m["key"] delete(m, "key") len(m) clear(m)
|
⚡ 四、三者对比速查
| 特性 | 数组 | 切片 | 映射 |
|---|
| 长度 | 固定 | 动态 | 动态 |
| 类型 | 值类型 | 引用类型 | 引用类型 |
| 初始化 | [n]T{...} | []T{...} / make | map[K]V{...} / make |
| 追加 | ❌ | append | m[k] = v |
| 删除 | ❌ | 切片拼接 | delete(m, k) |
| 查找 | 遍历 O(n) | 遍历 O(n) | m[k] O(1) |
| 遍历 | for-range | for-range | for-range(无序) |
🎯 五、实战小贴士
- 优先用切片,少用数组 — 除非长度固定(如
[3]byte 表示 RGB) - 预分配容量 — 知道大概存多少,用
make([]T, 0, cap) 避免频繁扩容 - map 查找用 comma-ok — 别靠零值判断 key 是否存在
- 遍历 map 要有序? 先取 key 排序
- 切片截取共享底层数组 — 要独立副本用
copy - nil map 写入会 panic — 必须先
make