一眼区分:[3]int是数组,[]int是切片;数组是固定内存块、值类型,传参全量拷贝;切片是三字段结构体(指针+len+cap)、引用类型,传参仅拷贝24字节且共享底层数组。

怎么一眼看出变量是数组还是切片
看声明语法最直接:[3]int 是数组,[]int 是切片。但真正决定行为的不是写法,而是底层结构:前者是固定内存块,后者是三字段结构体(指针 + len + cap)。哪怕你从数组截取:s := arr[0:3],得到的仍是切片——它不复制数据,只记录起始位置和长度。
传参时修改为什么有时生效、有时不生效
传 [3]int 进函数,函数内改 arr[0] = 999,调用方完全看不到变化,因为整个数组被完整拷贝到栈上;而传 []int,函数内写 s[0] = 999,调用方立刻看到,因为 s 的 array 字段指向同一底层数组。
- 数组传参开销随长度线性增长,
[1e6]int传一次 ≈ 8MB 内存拷贝 - 切片传参永远只拷贝 24 字节(64位系统下指针+两个 int)
- 如果想让数组修改生效,得显式传指针:
func f(p *[3]int)
append 后原变量“失效”的真实原因
不是 append 本身有问题,而是扩容时机不可见:len(s) == cap(s) 时再 append,运行时必须分配新底层数组、复制旧数据、返回新切片头。若你没把返回值赋给原变量,比如写成 append(s, x) 而非 s = append(s, x),那 s 还指着旧内存,后续操作可能覆盖别人正在用的数据,或读到 stale 值。
- 扩容策略不是翻倍,而是按长度阶梯增长(如 len=1024 → cap=1280)
-
make([]int, 0, 100)预留容量可避免早期扩容 - 并发场景下,多个 goroutine 共享同一底层数组却忽略
append返回值,极易引发竞态
什么时候非用数组不可
绝大多数业务逻辑用切片就够了。数组只在三个硬性约束下不可替代:
立即学习“go语言免费学习笔记(深入)”;
- 要当
map的 key ——map[[3]byte]string合法,map[][]byte编译报错 - 协议层要求精确字节对齐与长度,比如 TCP 头部
[20]byte或 RGB 像素[3]uint8 - 极致栈驻留需求:小数组(如
[4]int)编译器大概率分配在栈上,无 GC 压力;切片底层数组总在堆上,哪怕长度为 0
容易被忽略的是:切片的“动态”不等于安全——共享底层数组、扩容时机不确定、append 必须接收返回值,这三者叠加,才是 Go 中最常引发隐蔽 bug 的组合。


















