【golang】slice与array区别
在 Go 里,**数组(array)和切片(slice)**看起来很像,但本质上完全不是一回事,这是很多人一开始容易混淆的点。
我按几个维度给你梳理一下:
1. 定义方式 & 是否定长
// 数组:长度是类型的一部分,固定长度
var a1 [3]int // 长度为 3 的 int 数组
a2 := [3]int{1, 2, 3} // 字面量
// 切片:动态长度,本质是对底层数组的一层“视图”
var s1 []int // nil 切片
s2 := []int{1, 2, 3} // 字面量
s3 := make([]int, 3) // 长度为 3,容量为 3 的切片
数组:
[3]int和[4]int是两种不同的类型。切片:
[]int只有一种类型,长度可变。
2. 值语义 vs 引用语义(传参时的差异)
数组是值类型:
传给函数会被 拷贝 一份,函数内部改的是副本。
func modifyArray(a [3]int) {
a[0] = 100
}
func main() {
arr := [3]int{1, 2, 3}
modifyArray(arr)
fmt.Println(arr) // [1 2 3] 不变
}
切片是引用类型(更准确地说:包含对底层数组的引用):
传参时拷贝的是一个结构体,但这个结构体内部持有底层数组的指针,所以对元素的修改会反映到外面。
func modifySlice(s []int) {
s[0] = 100
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s) // [100 2 3] 被修改
}
总结一下:
数组传参:不会影响原数据(除非用指针
*[3]int)。切片传参:一般会影响原数据(因为指向同一个底层数组)。
3. 内部结构:数组是数据本体,切片是“描述符”
可以简单理解为:
数组:就是一块连续内存,自己就是数据本体。
切片:是一个结构体,通常包含三部分:
指向底层数组的指针
长度(len)
容量(cap)
切片的定义类似于:
type sliceHeader struct {
Data uintptr // 指向底层数组
Len int
Cap int
}
因此:
切片只是“视图”,多个切片可以共享同一个底层数组。
对切片元素的修改,实际是在改底层数组。
4. 长度和容量
数组
len(arr):数组长度没有
cap概念,因为数组大小固定。
切片
len(s):当前使用的长度cap(s):底层数组的容量(最多能放多少元素,未重新分配前)
s := make([]int, 2, 5)
fmt.Println(len(s)) // 2
fmt.Println(cap(s)) // 5
5. 扩容行为(只存在于切片)
数组长度固定,不能扩容。
切片可以 append,如果容量不够,Go 会:
分配一个更大的底层数组
拷贝原有数据
返回新的切片(指向新的数组)
注意:扩容后新旧切片不再共享底层数组。
s := []int{1, 2, 3}
s2 := s // s2 和 s 共用底层数组
s = append(s, 4) // 可能触发扩容
s[0] = 100
fmt.Println(s) // [100 2 3 4]
fmt.Println(s2) // 如果扩容了,这里还是 [1 2 3]
所以:
在容量足够时,
append后新旧切片还是共享底层数组。在触发扩容后,新切片使用新的底层数组,旧切片继续指向旧的。
6. 在函数签名和 API 设计上的使用场景
什么时候用数组?
固定长度并且长度是类型的一部分有意义,比如:
IPv4 地址
[4]byteMD5 哈希值
[16]byte
多见于底层库、协议实现、性能要求极高的场景。
更多时候用切片:
绝大多数业务代码都用切片:
动态列表
集合
返回多个值
标准库很多接口也是
[]byte、[]T而不是[N]T。
7. 作为 map 的 key
数组可以作为 map 的 key(因为是值类型,支持比较):
m := make(map[[2]int]string)
m[[2]int{1, 2}] = "point"
切片不能作为 map 的 key(切片不可比较):
m := make(map[[]int]string) // 编译错误:invalid map key type []int
8. nil 切片 vs 空切片
var s1 []int // nil 切片
s2 := []int{} // 空切片
s3 := make([]int,0) // 空切片
s1 == nil为trues2和s3不为 nil,但len都是 0
在原理上:
nil 切片:
Data = 0,Len = 0,Cap = 0空切片:
Data指向某个合法地址(一般是一个零长数组),Len = 0,Cap >= 0
使用上:
大多数场景下区别不大
有些 JSON 序列化 / 数据库框架对
nil和[]的处理会不一样
9. 小结一句话版
数组:
固定长度,长度是类型的一部分
值类型,传参会拷贝
本身就是底层数据
切片:
动态长度,灵活,日常开发基本都用它
引用到底层数组,对元素的修改会影响共享同一底层数组的其它切片
可以自动扩容(但可能导致底层数组更换)
切片本质是一个 (指针 + 长度 + 容量) 的结构,指向一个底层数组。
多个切片可以 共享同一个底层数组。
append在容量足够时复用原数组;容量不够时会 分配新数组并拷贝数据。切片变量传参/赋值本身是值拷贝,但因为里面有“指针”,所以表现出“引用语义”。