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

166 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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% 的场景下你应该用**切片**。数组在性能敏感的低层代码或需要固定内存布局时有用武之地,但日常业务逻辑中切片更灵活、更安全。