切片(Slice)是 Go 语言中最常用的数据结构之一。它比数组更灵活、更强大,是 Go 编程中处理序列数据的首选。

什么是切片?

切片是对底层数组的引用,它有三个关键属性:

┌─────────────────────────────────────────┐
│              切片结构                    │
├─────────────────────────────────────────┤
│  指针 (ptr)    → 指向底层数组            │
│  长度 (len)    → 切片中元素的个数        │
│  容量 (cap)    → 从起始位置到底层数组末尾│
└─────────────────────────────────────────┘

切片 vs 数组

对比项数组切片
长度固定可变
类型[n]T[]T
传递值类型(复制)引用类型(共享)
比较可比较不可比较(只能与 nil 比较)

创建切片

方式一:从数组创建

package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}

    s1 := arr[1:4]
    s2 := arr[:3]
    s3 := arr[2:]
    s4 := arr[:]

    fmt.Printf("arr: %v\n", arr)
    fmt.Printf("s1: %v\n", s1)
    fmt.Printf("s2: %v\n", s2)
    fmt.Printf("s3: %v\n", s3)
    fmt.Printf("s4: %v\n", s4)
}

输出:

arr: [10 20 30 40 50]
s1: [20 30 40]
s2: [10 20 30]
s3: [30 40 50]
s4: [10 20 30 40 50]

切片语法:

arr[low:high]   // 从 low 到 high-1
arr[low:]       // 从 low 到末尾
arr[:high]      // 从开头到 high-1
arr[:]          // 整个数组

方式二:直接声明

package main

import "fmt"

func main() {
    var s1 []int
    s2 := []int{10, 20, 30}
    s3 := []string{"a", "b", "c"}

    fmt.Printf("s1: %v, len: %d, cap: %d\n", s1, len(s1), cap(s1))
    fmt.Printf("s2: %v, len: %d, cap: %d\n", s2, len(s2), cap(s2))
    fmt.Printf("s3: %v, len: %d, cap: %d\n", s3, len(s3), cap(s3))
}

输出:

s1: [], len: 0, cap: 0
s2: [10 20 30], len: 3, cap: 3
s3: [a b c], len: 3, cap: 3

方式三:make 函数

package main

import "fmt"

func main() {
    s1 := make([]int, 5)
    s2 := make([]int, 5, 10)

    fmt.Printf("s1: %v, len: %d, cap: %d\n", s1, len(s1), cap(s1))
    fmt.Printf("s2: %v, len: %d, cap: %d\n", s2, len(s2), cap(s2))
}

输出:

s1: [0 0 0 0 0], len: 5, cap: 5
s2: [0 0 0 0 0], len: 5, cap: 10

make 语法:

make([]T, length)
make([]T, length, capacity)

切片操作

访问元素

package main

import "fmt"

func main() {
    nums := []int{10, 20, 30, 40, 50}

    fmt.Printf("第一个元素: %d\n", nums[0])
    fmt.Printf("最后一个元素: %d\n", nums[len(nums)-1])
}

修改元素

package main

import "fmt"

func main() {
    nums := []int{10, 20, 30}
    fmt.Printf("修改前: %v\n", nums)

    nums[0] = 100
    fmt.Printf("修改后: %v\n", nums)
}

遍历切片

package main

import "fmt"

func main() {
    nums := []int{10, 20, 30, 40, 50}

    fmt.Println("for 循环:")
    for i := 0; i < len(nums); i++ {
        fmt.Printf("索引: %d, 值: %d\n", i, nums[i])
    }

    fmt.Println("\nfor-range:")
    for index, value := range nums {
        fmt.Printf("索引: %d, 值: %d\n", index, value)
    }
}

append 追加元素

append 函数用于向切片追加元素:

追加单个元素

package main

import "fmt"

func main() {
    nums := []int{10, 20, 30}
    fmt.Printf("追加前: %v, len: %d, cap: %d\n", nums, len(nums), cap(nums))

    nums = append(nums, 40)
    fmt.Printf("追加后: %v, len: %d, cap: %d\n", nums, len(nums), cap(nums))
}

追加多个元素

package main

import "fmt"

func main() {
    nums := []int{10, 20}
    nums = append(nums, 30, 40, 50)
    fmt.Printf("追加多个: %v\n", nums)
}

追加另一个切片

package main

import "fmt"

func main() {
    nums1 := []int{1, 2, 3}
    nums2 := []int{4, 5, 6}

    nums1 = append(nums1, nums2...)
    fmt.Printf("合并切片: %v\n", nums1)
}

容量扩容

当切片容量不足时,append 会自动扩容:

package main

import "fmt"

func main() {
    nums := make([]int, 0, 2)

    for i := 0; i < 10; i++ {
        nums = append(nums, i)
        fmt.Printf("len: %d, cap: %d, value: %v\n", len(nums), cap(nums), nums)
    }
}

输出:

len: 1, cap: 2, value: [0]
len: 2, cap: 2, value: [0 1]
len: 3, cap: 4, value: [0 1 2]
len: 4, cap: 4, value: [0 1 2 3]
len: 5, cap: 8, value: [0 1 2 3 4]
...

扩容规则:容量小于 1024 时翻倍,大于 1024 时增加 25%。

copy 复制切片

copy 函数用于复制切片:

package main

import "fmt"

func main() {
    src := []int{1, 2, 3}
    dst := make([]int, len(src))

    n := copy(dst, src)
    fmt.Printf("复制了 %d 个元素\n", n)
    fmt.Printf("源切片: %v\n", src)
    fmt.Printf("目标切片: %v\n", dst)

    dst[0] = 100
    fmt.Printf("\n修改目标切片后:\n")
    fmt.Printf("源切片: %v\n", src)
    fmt.Printf("目标切片: %v\n", dst)
}

输出:

复制了 3 个元素
源切片: [1 2 3]
目标切片: [1 2 3]

修改目标切片后:
源切片: [1 2 3]
目标切片: [100 2 3]

切片删除元素

Go 没有内置的删除函数,需要用 append 实现:

删除指定索引

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3, 4, 5}
    index := 2

    nums = append(nums[:index], nums[index+1:]...)
    fmt.Printf("删除索引 %d 后: %v\n", index, nums)
}

输出:

删除索引 2 后: [1 2 4 5]

删除首尾元素

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3, 4, 5}

    nums = nums[1:]
    fmt.Printf("删除首元素: %v\n", nums)

    nums = nums[:len(nums)-1]
    fmt.Printf("删除尾元素: %v\n", nums)
}

输出:

删除首元素: [2 3 4 5]
删除尾元素: [2 3 4]

切片插入元素

package main

import "fmt"

func insert(slice []int, index, value int) []int {
    slice = append(slice, 0)
    copy(slice[index+1:], slice[index:])
    slice[index] = value
    return slice
}

func main() {
    nums := []int{1, 2, 4, 5}
    nums = insert(nums, 2, 3)
    fmt.Printf("插入后: %v\n", nums)
}

输出:

插入后: [1 2 3 4 5]

切片是引用类型

切片是引用类型,多个切片共享底层数组:

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[1:4]
    s2 := arr[2:5]

    fmt.Printf("arr: %v\n", arr)
    fmt.Printf("s1: %v\n", s1)
    fmt.Printf("s2: %v\n", s2)

    s1[1] = 100
    fmt.Printf("\n修改 s1[1] 后:\n")
    fmt.Printf("arr: %v\n", arr)
    fmt.Printf("s1: %v\n", s1)
    fmt.Printf("s2: %v\n", s2)
}

输出:

arr: [1 2 3 4 5]
s1: [2 3 4]
s2: [3 4 5]

修改 s1[1] 后:
arr: [1 2 100 4 5]
s1: [2 100 4]
s2: [100 4 5]

切片内存模型

底层数组: [1, 2, 3, 4, 5, 6, 7, 8]
           ↑           ↑
切片 s1: ptr───────┐   │
        len = 3    │   │
        cap = 5    └───┘

切片 s2: ptr────────────┘
        len = 3
        cap = 3

判断切片是否为空

package main

import "fmt"

func main() {
    var s []int

    if s == nil {
        fmt.Println("切片是 nil")
    }

    if len(s) == 0 {
        fmt.Println("切片是空的")
    }
}

实际案例

案例 1:去重

package main

import "fmt"

func unique(slice []int) []int {
    keys := make(map[int]bool)
    result := []int{}

    for _, v := range slice {
        if !keys[v] {
            keys[v] = true
            result = append(result, v)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 2, 3, 3, 3, 4, 4, 5}
    fmt.Printf("原切片: %v\n", nums)
    fmt.Printf("去重后: %v\n", unique(nums))
}

输出:

原切片: [1 2 2 3 3 3 4 4 5]
去重后: [1 2 3 4 5]

案例 2:反转切片

package main

import "fmt"

func reverse(slice []int) []int {
    for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
        slice[i], slice[j] = slice[j], slice[i]
    }
    return slice
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    fmt.Printf("原切片: %v\n", nums)
    fmt.Printf("反转后: %v\n", reverse(nums))
}

输出:

原切片: [1 2 3 4 5]
反转后: [5 4 3 2 1]

案例 3:过滤

package main

import "fmt"

func filter(slice []int, f func(int) bool) []int {
    result := []int{}
    for _, v := range slice {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    evens := filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Printf("偶数: %v\n", evens)

    greaterThan5 := filter(nums, func(n int) bool {
        return n > 5
    })
    fmt.Printf("大于 5: %v\n", greaterThan5)
}

输出:

偶数: [2 4 6 8 10]
大于 5: [6 7 8 9 10]

总结

本章学习了 Go 语言的切片:

知识点说明
创建[]T{}, make([]T, len, cap), 从数组切片
追加append(slice, elements...)
复制copy(dst, src)
删除append(slice[:i], slice[i+1:]...)
长度len(slice)
容量cap(slice)
类型引用类型,共享底层数组