第十七回:Go 语言切片(Slice)

就像切西瓜一样简单(动态数组)
唐伯虎
唐伯虎: 数组长度是死的,太不方便了!还是切片好,想吃多大块就切多大块,不够还能再接一块(Append)!

1. 🍉 庖丁解牛 (切片定义)

切片(Slice)是对数组的一个连续片段的引用。它包含三个要素:

arr := [5]int{10, 20, 30, 40, 50}
// 切取索引 1 到 3 (不包含 3)
// 就像数学里的 [1, 3)
s := arr[1:3] 

fmt.Println(s)      // [20 30]
fmt.Println(len(s)) // 2 (只有两个元素)
fmt.Println(cap(s)) // 4 (从索引1开始,后面还有 20, 30, 40, 50,共4个坑位)

2. 🔨 凭空造物 (make)

不用依赖现有的数组,直接创建一个切片。

// make(类型, 长度, 容量)
// 如果省略容量,则容量 = 长度
s := make([]int, 5, 10) 
fmt.Println(len(s), cap(s)) // 5, 10

3. ➕ 接木移花 (append 扩容)

切片满了怎么办?append 会自动扩容!

华夫人
华夫人: 小心!append 可能会导致换房子(内存重分配)。如果原来的底层数组装不下了,Go 会悄悄换一个更大的数组,把东西搬过去,然后让切片指向新家。
所以必须写成 s = append(s, val),更新你的房产证!
s := []int{1, 2}
fmt.Printf("地址: %p\n", s)

s = append(s, 3)       // 追加一个
s = append(s, 4, 5, 6) // 追加多个
fmt.Printf("新地址: %p\n", s) // 地址可能变了!

// 追加另一个切片(注意三个点,像展开卷轴一样)
s2 := []int{100, 200}
s = append(s, s2...) 

4. 📝 临摹真迹 (copy 深拷贝)

因为切片是引用,修改切片会影响原数组。如果你想彻底复制一份,互不干扰,要用 copy

src := []int{1, 2, 3}
// 必须先分配足够的空间,否则拷不进去!
dst := make([]int, len(src))

copy(dst, src) // 把 src 里的东西拷贝到 dst
dst[0] = 999
fmt.Println(src[0]) // 1 (原件不受影响)
⚠️ 陷阱:Nil 切片 vs 空切片

var s []int 是 nil 切片(没分配内存)。

s := []int{} 是空切片(分配了内存,但长度为0)。

虽然它们 len 都是 0,打印出来也都是 [],但在 JSON 序列化时,一个是 null,一个是 []

🎯 练功房(切西瓜)

从数组 [10, 20, 30, 40, 50] 中切出 [20, 30]

package main
import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}
    // 填空:切片范围
    s := arr[______]
    fmt.Println(s)
}

任务: 方括号里填什么?

答案: 1:3

解析: 索引从 0 开始。20 是索引 1,30 是索引 2。Go 语言切片是“左闭右开”,所以要写到 3 (不包含 3)。