数组与切片的区别
一句话结论:
数组是"值类型容器"
切片是"指向数组的引用视图"
再直白一点:
-
数组:我自己就是数据
-
切片:我只是看数据的一扇窗
-
数组 = 盒子
-
切片 = 指向盒子的标签
-
复制盒子 → 内容独立
-
复制标签 → 看同一个盒子
一、最本质的 4 个区别(核心)
|---------|---------------|---------------|
| 对比点 | 数组(array) | 切片(slice) |
| 类型 | 值类型 | 引用语义(结构体) |
| 长度 | 固定,类型的一部分 | 可变 |
| 内存 | 自己持有数据 | 指向底层数组 |
| 常用性 | 很少直接用 | Go 的主力 |
二、类型层面的区别(非常关键)
数组:长度是类型的一部分
var a [3]int
var b [4]int
👉 a 和 b 是完全不同的类型
a = b // ❌ 编译错误
切片:长度不是类型的一部分
var s1 []int
var s2 []int
👉 无论多长,类型都一样
三、内存模型区别(理解一切 bug 的根源)
数组(独立内存)
a := [3]int{1, 2, 3}
b := a
b[0] = 100
结果:
a = [1 2 3]
b = [100 2 3]
👉 值拷贝
切片(共享底层数组)
a := []int{1, 2, 3}
b := a
b[0] = 100
结果:
a = [100 2 3]
b = [100 2 3]
👉 共享底层数组
四、作为函数参数的区别
数组传参:完整拷贝(非常重)
func f(a [3]int) {
a[0] = 100
}
func main() {
x := [3]int{1, 2, 3}
f(x)
fmt.Println(x)
}
输出:
[1 2 3]
切片传参:拷贝 slice 头(轻)
func f(s []int) {
s[0] = 100
}
func main() {
x := []int{1, 2, 3}
f(x)
fmt.Println(x)
}
输出:
[100 2 3]
五、长度与容量(数组没有 cap)
a := [3]int{1, 2, 3}
len(a) // 3
cap(a) // ❌ 不存在
s := []int{1, 2, 3}
len(s) // 3
cap(s) // >= 3
👉 cap 是 slice 独有的概念
六、创建方式差异
数组(少见)
a := [3]int{1, 2, 3}
a := [...]int{1, 2, 3} // 编译期推断
切片(主流)
s := []int{1, 2, 3}
s := make([]int, 3)
s := make([]int, 0, 10)
七、为什么 Go 设计数组 + 切片两套?
一句话:
数组用于"确定大小的数据结构"
切片用于"动态、高效的数据处理"
数组适合:
- 小型、固定大小
- 值语义(拷贝安全)
- 底层数据结构(如哈希桶)
切片适合:
- 几乎所有业务代码
- 传参
- 集合、列表、缓冲区
八、工程级使用建议
✅ 99% 场景用切片
func process(data []byte)
✅ 用数组的 3 种典型场景
1️⃣ 编译期固定大小
var buf [32]byte // hash / uuid
2️⃣ 明确要值语义
type Point struct {
XY [2]int
}
3️⃣ 性能 / 底层库
var table [256]byte