Go中数组和切片是语义、内存模型、传参行为均不同的两类类型:数组传参复制整个内存块,大数组需显式传指针;切片传参仅24字节,但扩容不反映到调用方,共享底层数组易引发隐蔽bug。

Go 里数组和切片不是“同一种东西的两种写法”,而是语义、内存模型、传参行为都不同的两类类型——选错会直接导致性能问题、共享意外或编译失败。
数组传参会复制整个内存块,大数组不加 * 就是性能雷区
声明 func f(arr [1000]int),每次调用都拷贝 1000 个整数;而 func f(s []int) 只传 24 字节(ptr + len + cap)。栈空间暴涨、CPU 缓存失效都可能发生。
- 小数组(如
[3]byte表示 RGB)可放心值传,语义清晰又安全 - 大数组必须显式传指针:
func f(p *[1000]int,否则高频调用时性能断崖式下跌 - 数组作为 struct 字段时,
[32]byte比[]byte更省内存(无指针、无头信息)
append 后原切片不更新?因为切片头是值传递
切片变量本身是值类型(24 字节结构体),传参时只复制 ptr/len/cap。所以:
-
s[0] = 999能改到底层数组 → 外部可见 -
s = append(s, x)若触发扩容,新底层数组只在函数内生效 → 外部完全不受影响 - 真要让调用方看到扩容后的结果,得传
*[]int并手动赋值:*s = append(*s, x)
多个切片共享底层数组,改一个可能悄悄影响另一个
这是最隐蔽的 bug 来源。例如:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
s1 := arr[2:5] s2 := arr[4:7]
它们重叠在 arr[4] 上;修改 s1[2] 实际就是改 s2[0]。
- 用
len(s) == cap(s)判断是否即将扩容,比盲目append更可控 - 截取时避免无意共享:需要独立副本就用
copy(dst, src)或append([]T(nil), src...) - 函数返回切片时,若来自入参(如
return data[lo:hi]),调用方可能意外破坏你内部状态
什么时候必须用 [N]T 而不能用 []T
只有当你需要「类型级长度约束」或「栈上确定内存布局」时,才该选数组:
-
sha256.Sum256底层是[32]byte,哈希长度绝不能变 - C 互操作中匹配
struct { char buf[256]; },必须用[256]C.char -
[4]byte表示 IPv4 地址,[16]byte是 UUID —— 固定长度即语义 -
[3]int和[4]int是不同类型,编译期就能防错;而[]int对长度完全无感
别为了“统一风格”把所有固定结构都改成切片——多一层指针跳转,少一点确定性。

















