Files
cs-note/hzh/GolangStar/Go语言基础/Go语言数组与切片.md
T

4.5 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go基础语法
数组
切片
2026-06-07 15:00

Go 语言数组与切片

概述

数组是固定长度的同类型序列,切片是动态增长的视图。本文对比两者的核心差异,重点讲解切片的 append、copy 和底层共享机制。

正文

数组 vs 切片:本质区别是什么?

数组 = 数据本身(值类型)
切片 = 指向底层数组的"窗口"(引用类型)

[!question] ❓ 思考 如果把数组传给函数,函数内修改元素会影响原数组吗?切片呢?

数组:长度是类型的一部分

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 是完全不同的类型,不能互相赋值。

数组是值类型——传给函数时会完整拷贝:

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 最常用的集合类型

创建方式

// 直接字面量
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 是从起始位置到数组末尾的空间。

graph LR
    S["切片 s"] --> P["ptr: 指向 arr[1]"]
    S --> L["len: 3"]
    S --> C["cap: 4"]
    P --> A["底层数组: [2 3 4 5 _]"]

append:动态扩容

shopping := []string{"牛奶"}
shopping = append(shopping, "面包")          // ["牛奶", "面包"]
shopping = append(shopping, "水果", "蔬菜")  // 一次追加多个

[!tip] 💡 append 的规则

  • 如果剩余容量足够,直接在原数组上扩展
  • 如果不够,Go 会分配一块更大的新数组(通常是 2x),把数据复制过去
  • 返回值必须接收:append 可能返回新的切片头(因为底层数组换了)

copy:安全复制

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 面试最高频的问题之一:

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() 或重新创建:

safe := make([]int, len(s1))
copy(safe, s1)

append 后的共享问题

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 全变了

实战:遍历与处理

// 计算平均分
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% 的场景下你应该用切片。数组在性能敏感的低层代码或需要固定内存布局时有用武之地,但日常业务逻辑中切片更灵活、更安全。