go中数组和切片是语义、内存模型、传参行为均不同的两类类型:数组是值类型,传参复制整个内存块;切片是三字段结构体,传参仅复制24字节且共享底层数组。

Go 里数组和切片不是“同一种东西的两种写法”,而是语义、内存模型、传参行为都不同的两类类型——选错会直接导致性能问题、共享意外或编译失败。
数组是值类型,传参复制整个内存块
声明 var arr [1000]int 后传给函数,每次调用都会在栈上拷贝 1000 个 int(8KB)。这不是“引用传递”,改函数内 arr[0] 不会影响原数组。
- 小数组(如
[3]byte表示 RGB)传参没问题,但超过几十元素就得警惕 - 想避免拷贝?只能传指针:
func f(p *[1000]int,但这时你操作的是原始数组,得自己管边界 -
[3]int和[4]int是完全不同的类型,不能赋值、不能混用作 map key 或 struct 字段
切片是三字段结构体,传参只复制 24 字节
[]int 底层本质是 struct{ ptr *int; len, cap int }。传参时只复制这 24 字节(64 位系统),不复制底层数组数据。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 所以
func f(s []int) { s[0] = 99 }能改到原底层数组 —— 因为ptr指向同一地址 - 但
func f(s []int) { s = append(s, 1) }如果触发扩容,新底层数组只在函数内可见;返回后原切片不变 - 真要让扩容也生效?得传
*[]int,再用*s = append(*s, x),但多数场景应让函数返回新切片
len 和 cap 不是装饰,它们决定是否扩容和是否共享
len(s) 是当前可读写的元素数,cap(s) 是从 s 起始位置到底层数组末尾还能塞多少元素。两者相等时,下一次 append 必定触发扩容。
-
s := []int{1,2,3}→len==3,cap==3,追加第 4 个就 malloc 新数组 - 已知要存 100 个元素?写
s := make([]int, 0, 100),后续 100 次append都不会扩容 -
a := arr[2:5]和b := arr[4:7]共享arr[4],改a[2]就等于改b[0]—— 这类 bug 很难复现 - 需要独立副本?用
copy(dst, src)或append([]int(nil), src...)
什么时候必须用数组而不是切片
不是“风格偏好”,而是类型系统强制要求:当你需要编译期长度约束、确定内存布局或与 C/硬件交互时,[N]T 不可替代。
-
sha256.Sum256是[32]byte,哈希长度绝不能变;换成[]byte就失去类型安全 - C 互操作中,
C.struct{ buf [256]byte }必须用 Go 数组匹配,切片无法直接嵌入 C struct - 作为 struct 字段时,
[16]byte(如 UUID)比[]byte少一个指针、无额外头信息,更省内存 - 数组能做 map key(
map[[32]byte]string),切片不能
最常被忽略的一点:nil 切片和空切片行为不同。var s []int 是 nil,len(s) 和 cap(s) 都为 0,且 s == nil 为 true;而 s := []int{} 或 make([]int, 0) 是非 nil 切片,有真实底层数组(哪怕容量为 0)。JSON 解码时,空数组 [] 和缺失字段解出来的就是这两类,仅靠 len(s) == 0 判断是否“存在”会出错。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










