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

166 lines
4.5 KiB
Markdown
Raw Normal View History

2026-06-07 11:08:10 +08:00
---
2026-06-07 12:14:39 +08:00
tags: [go, golang, go基础语法, 数组, 切片]
create time: 2026-06-07 15:00
2026-06-07 11:08:10 +08:00
---
2026-06-07 12:14:39 +08:00
# Go 语言数组与切片
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
## 概述
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
数组是固定长度的同类型序列,切片是动态增长的视图。本文对比两者的核心差异,重点讲解切片的 append、copy 和底层共享机制。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
## 正文
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 数组 vs 切片:本质区别是什么?
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```
数组 = 数据本身(值类型)
切片 = 指向底层数组的"窗口"(引用类型)
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!question] ❓ 思考
> 如果把数组传给函数,函数内修改元素会影响原数组吗?切片呢?
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 数组:长度是类型的一部分
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
var scores [3]int // [0 0 0],零值初始化
prices := [3]float64{10.99, 20.99, 30.99}
names := [...]string{"张三", "李四"} // [...] 让编译器自动计算长度
colors := [5]string{0: "红", 2: "蓝"} // 指定位置赋值 → [红 "" 蓝 "" ]
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
**关键特性**:`[3]int` 和 `[5]int` 是**完全不同的类型**,不能互相赋值。
数组是**值类型**——传给函数时会完整拷贝:
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
func sum(a [3]int) int {
total := 0
for _, v := range a {
total += v
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
return total
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
arr := [3]int{1, 2, 3}
fmt.Println(sum(arr)) // 6 — arr 被完整拷贝了一份
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!warning] ⚠️ 大数组传参陷阱
2026-06-07 15:42:08 +08:00
> 如果数组很大,拷贝代价很高。解决方案:传 `*[3]int`(数组的指针)或改用切片。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 切片:Go 最常用的集合类型
#### 创建方式
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
// 直接字面量
2026-06-07 11:08:10 +08:00
fruits := []string{"苹果", "香蕉", "橙子"}
2026-06-07 12:14:39 +08:00
// make:指定 len 和可选 cap
nums := make([]int, 3, 5) // len=3, cap=5 → [0 0 0]
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
// 从数组/切片截取
2026-06-07 11:08:10 +08:00
arr := [5]int{1, 2, 3, 4, 5}
2026-06-07 12:14:39 +08:00
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 _]"]
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
#### append:动态扩容
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
shopping := []string{"牛奶"}
shopping = append(shopping, "面包") // ["牛奶", "面包"]
shopping = append(shopping, "水果", "蔬菜") // 一次追加多个
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!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
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!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 也被改了!
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
**原因**:`s1` 和 `s2` 共享同一个底层数组。修改一个会影响另一个。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!warning] ⚠️ 截取时的容量问题
> 如果需要独立副本,务必用 `copy()` 或重新创建:
> ```go
> safe := make([]int, len(s1))
> copy(safe, s1)
> ```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
#### append 后的共享问题
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```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
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 何时用数组?何时用切片?
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
| 场景 | 推荐 |
|------|------|
| 长度固定且已知(如一周 7 天) | 数组 `[7]int` |
| 需要频繁增删元素 | 切片 `[]int` |
| 函数参数(避免拷贝) | 切片 |
| 需要共享数据视图 | 切片截取 |
| 内存布局严格可控 | 数组 |
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!tip] 💡 实际开发经验
> 90% 的场景下你应该用**切片**。数组在性能敏感的低层代码或需要固定内存布局时有用武之地,但日常业务逻辑中切片更灵活、更安全。