--- tags: [go, golang, go基础语法, 数组, 切片] create time: 2026-06-07 15:00 --- # Go 语言数组与切片 ## 概述 数组是固定长度的同类型序列,切片是动态增长的视图。本文对比两者的核心差异,重点讲解切片的 append、copy 和底层共享机制。 ## 正文 ### 数组 vs 切片:本质区别是什么? ``` 数组 = 数据本身(值类型) 切片 = 指向底层数组的"窗口"(引用类型) ``` > [!question] ❓ 思考 > 如果把数组传给函数,函数内修改元素会影响原数组吗?切片呢? ### 数组:长度是类型的一部分 ```go var scores [3]int // [0 0 0],零值初始化 prices := [3]float64{10.99, 20.99, 30.99} names := [...]string{"张三", "李四"} // [...] 让编译器自动计算长度 colors := [5]string{0: "红", 2: "蓝"} // 指定位置赋值 → [红 "" 蓝 "" ] ``` **关键特性**:`[3]int` 和 `[5]int` 是**完全不同的类型**,不能互相赋值。 数组是**值类型**——传给函数时会完整拷贝: ```go func sum(a [3]int) int { total := 0 for _, v := range a { total += v } return total } arr := [3]int{1, 2, 3} fmt.Println(sum(arr)) // 6 — arr 被完整拷贝了一份 ``` > [!warning] ⚠️ 大数组传参陷阱 > 如果数组很大,拷贝代价很高。解决方案:传 `*[3]int`(数组的指针)或改用切片。 ### 切片:Go 最常用的集合类型 #### 创建方式 ```go // 直接字面量 fruits := []string{"苹果", "香蕉", "橙子"} // make:指定 len 和可选 cap nums := make([]int, 3, 5) // len=3, cap=5 → [0 0 0] // 从数组/切片截取 arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] // [2 3 4] — 仍是数组的视图 ``` > [!note] 📝 切片的三字段结构 > 切片底层由三个字段组成: > ``` > 切片 = {指针 → 底层数组起始位置, 长度(len), 容量(cap)} > ``` > `len` 是可访问的元素个数,`cap` 是从起始位置到数组末尾的空间。 ```mermaid graph LR S["切片 s"] --> P["ptr: 指向 arr[1]"] S --> L["len: 3"] S --> C["cap: 4"] P --> A["底层数组: [2 3 4 5 _]"] ``` #### append:动态扩容 ```go shopping := []string{"牛奶"} shopping = append(shopping, "面包") // ["牛奶", "面包"] shopping = append(shopping, "水果", "蔬菜") // 一次追加多个 ``` > [!tip] 💡 append 的规则 > - 如果剩余容量足够,直接在原数组上扩展 > - 如果不够,Go 会分配一块更大的新数组(通常是 2x),把数据复制过去 > - **返回值必须接收**:`append` 可能返回新的切片头(因为底层数组换了) #### copy:安全复制 ```go src := []int{1, 2, 3, 4, 5} dst := make([]int, 3) n := copy(dst, src) // dst=[1 2 3], n=3 ``` > [!warning] ⚠️ copy 只拷贝 min(lenSrc, lenDst) 个元素 > 如果目标比源短,只拷贝部分;如果目标更长,多余部分保持零值。 ### ⚠️ 切片的共享陷阱 这是 Go 面试最高频的问题之一: ```go src := []int{1, 2, 3, 4, 5} s1 := src[0:3] // [1 2 3], cap=5 s2 := s1[0:3] // [1 2 3], cap=5 s2[0] = 99 // 修改 s2 fmt.Println(s1) // [99 2 3] — s1 也被改了! ``` **原因**:`s1` 和 `s2` 共享同一个底层数组。修改一个会影响另一个。 > [!warning] ⚠️ 截取时的容量问题 > 如果需要独立副本,务必用 `copy()` 或重新创建: > ```go > safe := make([]int, len(s1)) > copy(safe, s1) > ``` #### append 后的共享问题 ```go a := make([]int, 3, 5) // len=3, cap=5 b := a[:3] // 共享底层数组 a = append(a, 99) // cap 够,不会重新分配 b[0] = 1 // 影响 a fmt.Println(a) // [1 99 0 99] — 注意 cap=5 全变了 ``` ### 实战:遍历与处理 ```go // 计算平均分 scores := []int{95, 89, 92, 88, 96} sum := 0 for _, score := range scores { sum += score } avg := float64(sum) / float64(len(scores)) fmt.Printf("平均分:%.2f\n", avg) // 92.00 ``` ### 何时用数组?何时用切片? | 场景 | 推荐 | |------|------| | 长度固定且已知(如一周 7 天) | 数组 `[7]int` | | 需要频繁增删元素 | 切片 `[]int` | | 函数参数(避免拷贝) | 切片 | | 需要共享数据视图 | 切片截取 | | 内存布局严格可控 | 数组 | > [!tip] 💡 实际开发经验 > 90% 的场景下你应该用**切片**。数组在性能敏感的低层代码或需要固定内存布局时有用武之地,但日常业务逻辑中切片更灵活、更安全。