首页 / Go 语言入门教程 / 切片基础

Go 语言入门教程

切片基础

本教程共 80 篇 · 第 28 篇 · 更新于 2026-07-27 · 约 7 分钟阅读

GoGo 入门教程切片Slicemakenil底层数组

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) 三个参数:

  1. 第一个是元素类型
  2. 第二个是长度(len)
  3. 第三个是容量(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