数组传参复制全部数据,切片传参只传24字节描述符(ptr/len/cap);大数组应传指针或转切片;append可能触发扩容导致底层数组更换,共享切片行为不可预测;需隔离时用copy显式复制。

数组赋值会复制全部数据,切片传参只传描述符
当你写 func foo(arr [5]int),函数内修改 arr[0] 不会影响调用方的原数组;但写 func bar(s []int),函数里 append(s, 99) 或 s[0] = 99 很可能改变原切片背后的数据——因为 []int 本身只是个 24 字节的结构体(ptr/len/cap),不存元素,只指底层数组。
- 大数组(比如
[1024 * 1024]byte)直接传参会触发整块内存拷贝,性能陡降;应传指针*[1024 * 1024]byte或转成切片[]byte - 切片传参看似轻量,但多个切片共享同一底层数组时,一个
s = append(s, x)可能触发扩容、分配新底层数组,导致其他切片“突然断连”,行为不可预测 - 若需隔离修改,用
copy(dst, src)显式复制数据,别依赖截取(s[1:3])就以为安全
切片扩容不是“加一”,而是按容量策略重新分配
append 超出当前 cap 时,Go 不是简单多开一个槽位,而是按规则申请新底层数组:容量 append 小切片可能触发多次内存分配+数据拷贝。
- 已知最终长度?用
make([]int, 0, expectedCap)预分配,避免扩容抖动 -
len(s) == cap(s)是扩容临界点,可通过此判断是否即将重分配 - 调试时用
fmt.Printf("%p, len=%d, cap=%d", &s[0], len(s), cap(s))查看底层数组地址变化
类型系统不兼容:[3]int 和 []int 完全是不同类型
Go 编译器把数组长度当作类型的一部分:[3]int 和 [4]int 不能互相赋值,也不能作为同名函数参数混用;而 []int 是独立类型,和任何具体长度的数组都无直接转换关系。
- 不能把
[3]int直接传给接收[]int的函数——需显式转成切片:foo(arr[:]) - 不能用
[]int{1,2,3}初始化[3]int变量,必须写[3]int{1,2,3} - JSON 解码时,如果结构体字段是
[3]string,输入少于 3 个元素会报错;换成[]string就能容忍空或任意长度
什么时候非用数组不可?
绝大多数场景该用切片,数组只在极少数硬性约束下才有存在价值:需要编译期确定大小、要求值语义隔离、或对接 C/系统调用接口。
立即学习“go语言免费学习笔记(深入)”;
- 固定协议头,如 HTTP/2 帧头是
[9]byte,长度错一位就解析失败 - 小尺寸且需栈上分配的临时缓冲,如
var buf [64]byte,避免逃逸到堆 - 作为 map 的 key(
[16]byte可做 key,[]byte不行) - RGB 颜色值
[3]uint8,传参时副本开销可忽略,且逻辑上绝不该被“扩容”
实际开发中,真正要手写 [N]T 的时刻远比想象中少。多数人掉坑,不是因为不会用切片,而是忘了它背后那块共享的底层数组——一次没注意的 append,可能让上游数据静默变异。










