Go中数组是值类型、长度固定,切片是引用类型、底层为三字段结构体;数组传参复制全部数据,切片传参仅复制结构体;append必须接收返回值,nil切片可安全append。

Go 语言里没有“动态数组”这种东西,[]T 是切片(slice),[N]T 才是数组(array);二者底层不同、语义不同、传参行为也不同——混用会导致静默错误或性能意外。
数组是值类型,赋值和传参会复制整个内存块
声明 [3]int 表示一个固定长度为 3 的整数数组,它在栈上分配,大小确定。一旦声明,长度不可变。
常见误用:把大数组当作参数传给函数,结果每次调用都拷贝全部数据。
-
[1024]byte作为函数参数?实际拷贝 1KB 内存 - 用
==比较两个数组?可以,但逐元素比,慢且易被忽略长度差异 - 想“扩容”数组?做不到。只能新建更大数组,再手动 copy
var a [3]int = [3]int{1, 2, 3}
b := a // b 是 a 的完整副本,修改 b 不影响 a
a[0] = 999
fmt.Println(a, b) // [999 2 3] [1 2 3]
切片是引用类型,底层指向数组,但自身是结构体
[]T 实际是运行时的一个三字段结构体:ptr(指向底层数组)、len(当前长度)、cap(容量)。它不拥有数据,只描述一段视图。
立即学习“go语言免费学习笔记(深入)”;
创建切片的常用方式有三种,行为差异明显:
-
make([]int, 3)→ len=3, cap=3,底层数组长度至少为 3 -
make([]int, 3, 10)→ len=3, cap=10,预留空间,后续append不触发扩容 -
a[1:4](从数组或切片截取)→ 新切片共享底层数组,修改可能互相影响
data := [5]int{0, 1, 2, 3, 4}
s1 := data[1:3] // [1 2], cap=4(从索引1到数组尾)
s2 := data[2:4] // [2 3], 与 s1 共享底层数组
s2[0] = 99
fmt.Println(s1) // [1 99] —— 被意外改了
append 不一定 realloc,但永远返回新切片
append 是切片唯一安全的“增长”操作。它检查容量是否足够:够就直接填,不够就分配新底层数组并 copy。关键点是——append 总是返回新切片,原变量不变。
典型错误:忘记接收返回值,导致追加失效。
-
s = append(s, x)✅ 必须赋值回去 -
append(s, x)❌ 原 s 不变,新切片被丢弃 - 对从数组截取的切片
append,若超出原数组容量,会自动 malloc 新底层数组
s := []int{1, 2}
s = append(s, 3) // 必须赋值
s = append(s, 4, 5) // 支持多值
fmt.Println(s) // [1 2 3 4 5]
切片的零值是 nil,但 len/cap 都为 0,可直接 append
var s []int 声明的是 nil 切片,不是空切片。但它和 []int{}(空切片)在大多数场景下行为一致:len=0、cap=0、可 append、可遍历(不进循环体)。
区别只在底层:nil 切片的 ptr 为 nil;空切片的 ptr 指向某个有效地址(如内部零长数组)。极少需要区分,除非做指针比较或 JSON 序列化(nil 切片序列化为 null,空切片为 [])。
- 用
len(s) == 0判断是否为空,而不是s == nil -
json.Marshal(nilSlice)→null;json.Marshal(emptySlice)→[] - 向 nil 切片
append是安全的,Go 会自动分配底层数组
var s1 []int
s2 := []int{}
fmt.Println(len(s1), cap(s1), s1 == nil) // 0 0 true
fmt.Println(len(s2), cap(s2), s2 == nil) // 0 0 false
s1 = append(s1, 1) // OK
最常被忽略的是:切片的 cap 不是“还能加多少”,而是“从当前起始位置开始,底层数组还剩多少可用空间”。截取、传递、append 时若没留意 cap 变化,可能意外复用旧内存,引发难以调试的数据污染。










