切片是Go语言中最常用的数据结构之一,具有灵活、轻量的特点。本文将介绍切片的定义、底层原理、常用操作、性能优化技巧,并配套示例代码。


一、切片的基本定义与声明

切片是对数组的一个轻量级抽象,具有动态大小的特性。

示例 1:切片的定义与初始化

package main

import "fmt"

func main() {
    s := []int{1, 2, 3}
    fmt.Println(s) // 输出: [1 2 3]
}

二、切片的底层结构

切片的本质是一个结构体,包含:

  • 一个指向底层数组的指针
  • 长度(len)
  • 容量(cap)
type slice struct {
    ptr *T
    len int
    cap int
}

多个切片可能共享同一个底层数组。


三、切片的切割操作

你可以通过对已有数组或切片进行“切片”来创建新的切片。

示例 2:从数组切割出切片

package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}
    s := arr[1:4] // 包含下标1至3,不含下标4
    fmt.Println(s) // 输出: [20 30 40]
}

四、使用 make 创建切片(推荐方式)

使用 make 创建切片时,可以指定长度和容量,提高性能。

示例 3:使用 make 创建切片

s := make([]int, 3, 10)
fmt.Println(len(s), cap(s)) // 输出: 3 10

五、切片的追加(append)

Go 中切片的大小可变,可以使用 append 动态添加元素。

示例 4:追加元素

s := []int{1, 2}
s = append(s, 3, 4)
fmt.Println(s) // 输出: [1 2 3 4]

示例 5:自动扩容特性

s := []int{}
for i := 0; i < 10; i++ {
    s = append(s, i)
}
fmt.Println(s) // 输出: [0 1 2 3 4 5 6 7 8 9]

六、切片共享底层数组的副作用

多个切片可能共享同一个数组,修改其中一个切片可能影响另一个。

示例 6:共享底层数组

a := []int{1, 2, 3, 4}
b := a[1:3]
b[0] = 99
fmt.Println(a) // 输出: [1 99 3 4]

七、使用 copy 复制切片,避免共享副作用

示例 7:用 copy 创建切片副本

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

dst[0] = 99
fmt.Println(src) // [1 2 3]
fmt.Println(dst) // [99 2 3]

八、清空切片但保留容量(复用切片)

可以使用 s = s[:0] 快速清空切片,适用于复用场景。

示例 8:复用切片空间

s := make([]int, 0, 100)
s = append(s, 1, 2, 3)
fmt.Println(s) // [1 2 3]

s = s[:0] // 清空但保留容量
fmt.Println(s) // []
fmt.Println(cap(s)) // 100

九、切片性能优化技巧

  1. 提前分配容量(避免频繁扩容)
s := make([]int, 0, 1000) // 高效方式
  1. copy 避免共享副作用(如上)
  2. 使用 s = s[:0] 实现复用

十、切片陷阱:内存泄漏风险

如果一个小切片引用了一个大的数组,可能导致整个大数组无法回收。

示例 9:避免切片内存泄漏

func subSliceSafe(s []byte) []byte {
    tmp := make([]byte, len(s))
    copy(tmp, s)
    return tmp
}

总结

技巧 建议
提前分配容量 使用 make([]T, 0, n)
避免副作用 copy 创建独立副本
切片复用 使用 s = s[:0] 清空但不释放底层数组
避免内存泄漏 不要让小切片持有大数组,必要时手动 copy

孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
腾讯云开发者社区:孟斯特