切片基础
本教程共 80 篇 · 第 28 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
28. 切片基础
本节目标:搞懂切片的底层三件套(指针、长度、容量),学会用 make 创建切片,分清 nil 切片和空切片的区别。
切片是什么
上一章学了数组,数组最大的问题是长度固定,改不了。实际开发中你很少直接用数组,用得最多的是切片(Slice)。
切片可以理解成数组的「窗户」。数组是一排连续的格子,切片就是你在上面框了一块区域,可以滑动、可以扩大。切片本身不存数据,它只是指向底层数组的一个引用。
来看一个最简单的例子:
package main
import "fmt"
func main() {
var s []int // 声明一个切片,此时是 nil
s = []int{1, 2, 3}
fmt.Println(s) // [1 2 3]
}
也可以在声明时直接赋值:
s := []int{10, 20, 30}
注意这里没有写长度,有长度的是数组,没长度的是切片。
底层结构:ptr、len、cap
切片的底层是一个结构体,包含三个字段:
- ptr:指向底层数组的指针
- len:当前长度,也就是切片里有几个元素
- cap:容量,底层数组从 ptr 开始算,最多能放多少元素
画个图你就明白了。假设底层数组是 [5]int{1, 2, 3, 4, 5},切片 s 指向这个数组:
底层数组: [1, 2, 3, 4, 5]
切片 s: ^--------^
ptr 指向第1个元素
len = 3(用了3个)
cap = 5(最多5个)
你可以用 len() 和 cap() 来查看:
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
fmt.Println("长度:", len(s)) // 长度: 5
fmt.Println("容量:", cap(s)) // 容量: 5
}
Note切片之所以有 len 和 cap 两个值,是因为它背后有个底层数组。len 是你「用了多少」,cap 是「还能用多少」。这跟数组不一样,数组的 len 和 cap 永远相等。
用 make 创建切片
当你不知道具体放什么值,但需要一个有初始空间的切片时,用 make:
// make([]T, length, capacity)
s := make([]int, 3) // len=3, cap=3,元素都是0
fmt.Println(s) // [0 0 0]
s2 := make([]int, 3, 5) // len=3, cap=5,元素都是0
fmt.Println(s2) // [0 0 0]
fmt.Println(len(s2), cap(s2)) // 3 5
make([]T, length, capacity) 三个参数:
- 第一个是元素类型
- 第二个是长度(len)
- 第三个是容量(cap),可以省略,省略时 cap 等于 len
Tip如果你提前知道最终大概要放多少元素,用 make 指定 cap 可以减少扩容次数,提升性能。这个后面讲扩容时会细说。
从数组创建切片
切片的另一个常见来源是从数组「切」出来:
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // 从下标1到下标4(不含4)
fmt.Println(s) // [2 3 4]
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 4
}
arr[1:4] 的意思是:从数组 arr 的下标 1 开始,到下标 4 结束(不包含 4)。
cap 为什么是 4?因为底层数组从下标 1 到末尾还有 4 个位置(下标 1、2、3、4)。
nil 切片和空切片
这是新手最容易搞混的地方。
nil 切片:只声明没初始化的切片。
var s []int
fmt.Println(s == nil) // true
fmt.Println(len(s)) // 0
fmt.Println(cap(s)) // 0
空切片:用 make 或字面量创建的、长度为 0 的切片。
s1 := make([]int, 0)
s2 := []int{}
fmt.Println(s1 == nil) // false
fmt.Println(s2 == nil) // false
fmt.Println(len(s1)) // 0
fmt.Println(len(s2)) // 0
两者的区别在于:nil 切片的内部指针是 nil,空切片的内部指针指向一个零长度的数组。但对日常使用来说,两者的行为几乎一样——长度都是 0,append 都能正常工作。
Warning不要用
len(s) == 0来判断一个切片是不是 nil。len(s) == 0对 nil 切片和空切片都返回 true。如果你确实需要区分,用s == nil。
常见坑
切片是引用类型
和数组不同,切片是引用类型。把切片赋值给另一个变量,它们共享底层数组:
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
s2 := s1
s2[0] = 100
fmt.Println(s1) // [100 2 3] s1 也变了!
}
这是因为 s1 和 s2 的内部指针指向同一个底层数组。改了一个,另一个也能看到。
Tip如果你需要一个独立的副本,用
copy函数,这个下一章会讲。
小结
切片是 Go 里用得最多的复合类型。记住这三点就够了:
- 切片底层是 ptr + len + cap 三件套
- 用
make([]T, len, cap)创建带初始空间的切片 - nil 切片和空切片行为几乎一样,但
== nil判断不同
下一章我们讲切片的增删改操作,包括最常用的 append。