Golang - 2.切片

Go 切片

语法规则

Go 切片表达式 a[low:high] 遵循 左闭右开 原则:

参数 含义 是否包含
low 起始索引 包含
high 结束索引 不包含

代码示例

s1 := [5]int{1, 2, 3, 4, 5}
s2 := s1[1:3]
fmt.Println(s2) // [2 3]
数组:   [1,  2,  3,  4,  5]
索引:    0   1   2   3   4
                   
           low     high (不包含)
  • 为什么这样设计
  • 左闭右开 [low, high) 的好处:
    • 长度计算简单:len = high - low
    • 连续切片不重叠:a[0:2] 和 a[2:5] 可以无缝拼接

Go 切片(Slice)详解

1. 切片是什么

切片是 Go 语言中动态大小的、灵活的数组视图。它是对底层数组的引用,本身不存储数据,而是描述数组的一段区间。

切片 vs 数组

特性 数组(Array) 切片(Slice)
长度 固定,是类型的一部分 动态,可变化
传递 值传递(复制整个数组) 引用传递(底层共享数据)
声明 [5]int []int
灵活性
arr := [5]int{1, 2, 3, 4, 5} // 数组
sli := []int{1, 2, 3, 4, 5}   // 切片

2. 切片的底层结构

切片在内存中是一个结构体(slice header),包含三个字段:

+-----------+---------+---------+
|  指针(ptr) | 长度(len) | 容量(cap) |
+-----------+---------+---------+
| 指向底层数组 | 元素个数  | 可用容量  |
+-----------+------------------+
// 伪代码表示
struct SliceHeader {
    Data uintptr  // 指向底层数组的指针
    Len  int      // 切片当前长度(元素个数)
    Cap  int      // 从指针位置到底层数组末尾的总容量
}

3. 创建切片的四种方式

方式一:字面量声明

s1 := []int{1, 2, 3, 4, 5}
fmt.Println(s1) // [1 2 3 4 5]

方式二:从数组切片

arr := [5]int{1, 2, 3, 4, 5}
s2 := arr[1:4]  // 左闭右开 [1, 4)
fmt.Println(s2) // [2 3 4]

方式三:使用 make

// make([]T, length, capacity)
s3 := make([]int, 5, 10) // 长度5,容量10,初始值全为0
fmt.Println(len(s3)) // 5
fmt.Println(cap(s3)) // 10

方式四:从切片切片

s4 := []int{1, 2, 3, 4, 5, 6, 7}
s5 := s4[2:5]
fmt.Println(s5) // [3 4 5]

4. 切片表达式深度解析

基本语法

a[low : high]
  • low:起始索引,包含(默认值 0)
  • high:结束索引,不包含(默认值 len(a))
arr := [5]int{10, 20, 30, 40, 50}

arr[1:3]  // [20 30]      low=1, high=3
arr[:3]   // [10 20 30]   low=0(默认), high=3
arr[2:]   // [30 40 50]   low=2, high=5(默认)
arr[:]    // [10 20 30 40 50]  low=0, high=5

为什么采用左闭右开?

数组:  [10, 20, 30, 40, 50]
索引:   0   1   2   3   4
            ↑       ↑
          low=1   high=3 (不包含)

结果: [20, 30]

优点:
1. 长度计算直观len = high - low(3 - 1 = 2)
2. 连续切片无重叠arr[0:2] + arr[2:5] 完整覆盖无遗漏


5. 切片是引用类型

切片操作不会复制数据,而是共享底层数组。

arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // s 指向 arr 的同一内存

s[0] = 999
fmt.Println(arr) // [1 999 3 4 5]  原数组被修改!
fmt.Println(s)   // [999 3 4]

内存示意图

arr: [1, 999, 3, 4, 5]
      ↑    ↑        ↑
      0    1        4
           │
      s: [999, 3, 4]
      len=3, cap=4

6. 容量(Cap)与长度(Len)

arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:3]

fmt.Println(s)       // [20 30]
fmt.Println(len(s))  // 2   (high - low = 3 - 1)
fmt.Println(cap(s))  // 4   (从索引1到数组末尾 = 5 - 1)

容量计算

cap = 底层数组长度 - low

arr := [6]int{0, 1, 2, 3, 4, 5}

s1 := arr[1:4]  // cap = 6 - 1 = 5
s2 := arr[2:3]  // cap = 6 - 2 = 4

7. append 与扩容机制

基本用法

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

扩容规则

append 超出容量时,Go 会分配新数组:

arr := [4]int{1, 2, 3, 4}
s := arr[:2] // [1, 2], len=2, cap=4

s = append(s, 5, 6) // 未超cap,仍指向原数组
fmt.Println(arr)    // [1 2 5 6]  原数组被改!

s = append(s, 7, 8, 9) // 超cap,分配新数组
fmt.Println(arr)       // [1 2 5 6]  原数组不变

扩容策略(Go 1.18+)

原容量 新容量策略
< 256 翻倍(2x)
>= 256 约 1.25x ~ 1.5x,平滑过渡

8. copy 函数

用于复制切片数据,避免共享底层数组。

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

copy(dst, src)

dst[0] = 999
fmt.Println(src) // [1 2 3 4 5]  原切片不受影响
fmt.Println(dst) // [999 2 3 4 5]

copy 返回实际复制的元素个数(取两个切片长度的最小值)。


9. 常见坑点

坑 1:切片导致原数组意外修改

func getSlice() []int {
    arr := [5]int{1, 2, 3, 4, 5}
    return arr[:3] // 返回后 arr 仍在堆上,外部可改
}

解决: 使用 append([]int(nil), s...)make + copy

坑 2:append 后切片脱离原数组

arr := [3]int{1, 2, 3}
s := arr[:]

s = append(s, 4) // 超cap,s 指向新数组
s[0] = 999

fmt.Println(arr) // [1 2 3]  未变!
fmt.Println(s)   // [999 2 3 4]

坑 3:reslice 超出容量 panic

s := make([]int, 3, 5)
_ = s[:10] // panic: slice bounds out of range [:10] with capacity 5

10. 完整示例代码

package main

import "fmt"

func main() {
    // 1. 创建
    arr := [5]int{10, 20, 30, 40, 50}
    s := arr[1:4]
    fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))

    // 2. 修改共享底层
    s[0] = 999
    fmt.Println("修改后 arr:", arr)

    // 3. append
    s = append(s, 60)
    fmt.Printf("append后 s = %v, len = %d, cap = %d\n", s, len(s), cap(s))

    // 4. 超cap,分配新数组
    s = append(s, 70, 80)
    s[0] = 111
    fmt.Println("新数组 s:", s)
    fmt.Println("arr 是否被改:", arr)

    // 5. copy
    dup := make([]int, len(s))
    copy(dup, s)
    dup[0] = 0
    fmt.Println("s:", s)
    fmt.Println("dup:", dup)
}

输出:

s = [20 30 40], len = 3, cap = 4
修改后 arr: [10 999 30 40 50]
append后 s = [999 30 40 60], len = 4, cap = 4
新数组 s: [111 30 40 60 70 80]
arr 是否被改: [10 999 30 40 50]
s: [111 30 40 60 70 80]
dup: [0 30 40 60 70 80]


总结

要点 说明
切片是引用 共享底层数组,修改相互影响
左闭右开 a[low:high],长度 = high - low
len vs cap len 是元素数,cap 是可用容量
append 可能扩容 超 cap 时会分配新数组,原数组不再受影响
copy 深拷贝 需要独立数据时用 copy

AI 助手 - deepseek-v4-flash

你好!有什么可以帮你的吗?

可以问我:推荐文章、搜索主题、了解博客内容

AI 生成内容仅供参考

未播放
0:00 / 0:00