Go 入门 05:数组、切片与 Map
Go 入门 05:数组、切片与 Map
数组(array)、切片(slice)、map 是 Go 最常用的内置容器类型。新手最容易踩坑的就是 slice,本篇会重点剖析其底层结构。
一、数组 array
数组是定长、同类型元素的集合,长度是类型的一部分:
vara[3]int// [0 0 0]b:=[3]int{1,2,3}c:=[...]int{1,2,3}// 让编译器推导长度
[3]int和[4]int是不同的类型,不能互相赋值。
数组是值类型,赋值与传参都会发生完整拷贝:
a:=[3]int{1,2,3}b:=a// 拷贝b[0]=99// a[0] 仍是 1实际开发中数组用得不多,更多用切片。
二、切片 slice
2.1 切片是什么
切片是对底层数组的引用视图,由三部分组成:
slice = { ptr, len, cap }ptr:指向底层数组的某个元素len:当前切片可见的元素个数cap:从 ptr 到底层数组末尾的元素个数
2.2 创建切片
// 字面量s:=[]int{1,2,3}// 基于数组切出arr:=[5]int{1,2,3,4,5}s=arr[1:4]// [2 3 4],len=3, cap=4// make 显式分配s=make([]int,3)// len=3, cap=3s=make([]int,3,10)// len=3, cap=102.3 append 与扩容
s:=[]int{1,2,3}s=append(s,4,5)当len == cap时再次 append 会触发扩容:
- 原 cap < 1024:新 cap = 2 × 原 cap
- 原 cap >= 1024:新 cap = 1.25 × 原 cap
Go 1.18 后扩容因子有调整,但思路一致:几何级增长,避免每次 append 都分配。
2.4 切片陷阱
陷阱 1:共享底层数组
a:=[]int{1,2,3,4,5}b:=a[1:3]// [2 3]b[0]=99fmt.Println(a)// [1 99 3 4 5] — a 被改了!陷阱 2:append 后是否共享取决于是否扩容
a:=[]int{1,2,3,4,5}b:=a[1:3]// len=2, cap=4b=append(b,100)// 未扩容,会修改 a[3]fmt.Println(a)// [1 2 3 100 5]最佳实践:明确想要副本时用copy:
b:=make([]int,len(a))copy(b,a)陷阱 3:循环里 range 变量复用
// 错误示例(Go 1.22 之前)funcs:=[]func(){}for_,v:=range[]int{1,2,3}{funcs=append(funcs,func(){fmt.Println(v)})}// 输出三次 3Go 1.22 已修复该行为,每次循环 v 都是独立变量。
2.5 删除元素
// 删除索引 is=append(s[:i],s[i+1:]...)三、map
3.1 创建与使用
// 字面量m:=map[string]int{"a":1,"b":2,}// makem=make(map[string]int)m["c"]=3// 读取(不存在返回零值)v:=m["x"]// 0v,ok:=m["x"]// 0, false — 用 ok 判断是否存在// 删除delete(m,"a")// 长度fmt.Println(len(m))3.2 遍历
fork,v:=rangem{fmt.Println(k,v)}⚠️ map 的遍历顺序是随机的!这是 Go 故意设计的,避免开发者依赖某个顺序。
3.3 map 不是并发安全的
并发读写 map 会触发 panic:
fatal error: concurrent map writes并发场景下使用:
sync.RWMutex+mapsync.Map(适合读多写少且 key 集合相对稳定的场景)
varmu sync.RWMutex m:=make(map[string]int)mu.Lock()m["a"]=1mu.Unlock()mu.RLock()v:=m["a"]mu.RUnlock()3.4 map 的 key 限制
key 必须是可比较类型:基本类型、指针、channel、interface(动态类型可比较)、struct(所有字段可比较)、array。
不能作为 key:slice、map、function。
四、字符串与 []byte / []rune
s:="Hello, 世界"b:=[]byte(s)// 字节切片,每个中文 3 字节r:=[]rune(s)// rune 切片,每个中文一个 runefmt.Println(len(b))// 13fmt.Println(len(r))// 9fmt.Println(string(b))// 还原回字符串五、实战:单词词频统计
packagemainimport("fmt""strings")funcmain(){text:="go is great go is fast go is fun"counts:=make(map[string]int)for_,w:=rangestrings.Fields(text){counts[w]++}forw,n:=rangecounts{fmt.Printf("%s -> %d\n",w,n)}}六、小结
| 类型 | 长度 | 是否引用 | 并发安全 |
|---|---|---|---|
| array | 固定 | 值类型 | - |
| slice | 可变 | 引用底层数组 | 否 |
| map | 可变 | 引用 | 否 |
- slice =
{ptr, len, cap},append 可能扩容也可能不扩容; - 想拷贝就用
copy; - map 遍历无序,并发要加锁;
- 字符串处理中文用
[]rune。
下一篇我们将进入 Go 的"OOP"世界:结构体与方法。
