数组与切片:固定大小的值,会变长的引用
数组与切片:固定大小的值,会变长的引用
这一章讲 Go 最常用的容器,以及一对必须分清的兄弟: 数组(array)定长,赋值即复制;切片(slice)变长,赋值只传引用。 混淆它俩是 Go 新手最高发的事故。主线例子会带你亲眼看一次共享引用的效果: 改一下数组,所有从它身上切出来的切片全部跟着变。
1. 数组:定长、从零号位开始、整体复制
书里给的定义:数组是一组同类型元素的集合,每个元素靠「索引」访问,索引从 0 开始; Go 的数组长度固定,而且是值类型——赋给另一个变量、或者传给函数, 复制的是整个数组,不是它的引用1。
声明与初始化,照原书全部写法2:
var arr [5]int // 定长 5 的整数数组,元素全是零值 → [0 0 0 0 0]
arr := [5]int{1, 2, 3} // 给 3 个,剩下补零值 → [1 2 3 0 0]
arr := [...]int{1, 4, 7, 2} // [...] 让编译器按初始化值的个数定长 → 长度 4[...] 里的三个点叫省略号(ellipsis):长度交给编译器数3。
另外两种「只占坑」的初始化:arr[1] = 2; arr[3] = 4 单独喂某些索引,
其余保持零值 → [0 2 0 4 0]2。
为什么需要数组
书里的小例子很有说服力:不用容器存四个学生名字,你得写
name1、name2、name3、name4 四个变量——
名字的数量在写代码那一刻就被定死了,运行时想加第五个?没门4。
数组把「一批同类的东西」收进一个名字里,这是所有容器的起点。
数组的三个日常操作
| 操作 | 写法 | 备注 |
|---|---|---|
| 长度 | len(arr) | 多维数组 len 返回最外层长度(二维数组的「行数」)5 |
| 比较 | arr1 == arr2 | 类型、长度、每个元素都相同才 true6 |
| 复制 | arr2 := arr1 | 整份复制;改 arr2 不影响 arr17 |
比较的例子值得看一眼,因为它藏着零值的影子:
[4]int{1,4,7} 和 [4]int{1,4,7,0} 是相等的——
前者最后一个元素没写,自动补了零值 06。
复制(赋值)的语义用一组数字走一遍(arr1 := [4]int{1,4,7,2}):
arr2 := arr1 // 复制:[1 4 7 2] 整份抄给 arr2
arr2[2] = 90 // 只改 arr2 的第三格
arr1 → [1 4 7 2] // 原样不动
arr2 → [1 4 90 2] // 只有副本变了图说:数组像一叠复印好的文件,谁拿走一份都是全新的7。
遍历:两种 for
for i := 0; i < len(arr); i++ { // 经典三件套
sum += arr[i]
}
for index, value := range arr { // range:一趟给你索引和值
sum += value
}range 一个容器,固定吐两个值:索引和元素。 只想要值?
把索引用空白标识符扔掉:for _, value := range arr——
这是 Go 代码里出现频率最高的循环写法8。
数组的判决书
书里给数组列了四条限制,加起来就是切片的出生证明9:
- 长度在编译期定死,运行时不能追加;
- 不能用变量定义长度——
[size]int里 size 是变量直接报错; - 越界访问/写入,编译器或运行时报 out of bound;
- 作为函数参数,整个数组被复制,大数组白白浪费内存和时间。
实战结论:Go 代码里几乎不写数组,切片才是默认容器。 数组活着的意义, 是作为切片的「底层货架」存在。
2. 切片:数组上的一个窗口
切片是数组的一个片段:自己不存数据,只是指向某段数组的「窗口」。 窗口有三个参数,记住这「三件套」,切片的行为全能推出来:
┌─────────────────────────────────────────────┐
│ 底层数组: [1 2 3 4 5 6 7 8 9 10] │
│ ▲ ▲ │
│ │ │ │
│ slice1 = arrayval[2:5] │ │
│ 指针 ──→ 指向元素 3 │ │
│ 长度 len = 3 (3,4,5) │ │
│ 容量 cap = 8 (从 3 到数组末尾,共 8 格) │
└─────────────────────────────────────────────┘
图说:切片 = 指针 + 长度 + 容量。长度是窗口里现在有几格,
容量是底层货架上从窗口开头到货架末尾还剩几格。长度和容量的区别是本章最要紧的概念,书里的话原样接住:
长度是切片引用到的元素个数;容量是「从切片的第一个元素算起,
底层数组到末尾还剩多少个位置」——也就是这个窗口最多还能往后扩多少10。
cap 函数取容量:len = 3、cap = 8 的例子就来自 arrayval[2:5] 对长度 10 的数组11。
主线例子:两个切片,一条数据,一改全变
这是本章的主线,照原书完整走一遍12:
arrayval := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := arrayval[1:8] // 索引 1 到 7 → [2 3 4 5 6 7 8],len 7
slice2 := slice1[:6] // 在切片上再切,取到第 6 格 → [2 3 4 5 6 7]
// 关键一步:动底层数组
arrayval[2] = 90三个变量此刻的样子:
arrayval → [1 2 90 4 5 6 7 8 9 10] ← 原数组的 3 变成了 90
slice1 → [2 90 4 5 6 7 8] ← 跟着变!
slice2 → [2 90 4 5 6 7] ← 也跟着变!图说:切片没有自己的数据,改数组就是改它们;反过来,通过切片改元素,
数组上也立刻可见——书里双向演示过(slice1[0] = 178 之后数组第一个元素变 178)13。
low:high 的切法:取 low 到 high-1,左闭右开;两个边界都能省略,
默认取 0 和数组长度,[:] 就是整个数组12。
判断(我们的,不是书里的): 「切片共享底层数组」是 Go 最高发的隐蔽 bug 源: 函数收到一个切片、往里 append 或改元素,调用方的数据悄悄变了。 防御姿势只有两个:要么函数里先
copy一份再动手, 要么在注释里写明「会修改传入切片的元素」。 如果错,会错在: 对小函数和内部代码,共享恰恰是高效和简洁的来源, 一刀切地防御反而臃肿。这是「知道风险之后的取舍」,不是绝对禁令。
3. make 与 append:切片的出生证和生长剂
切片的三种出生方式:
| 方式 | 例子 | 结果 |
|---|---|---|
| 字面量 | s := []int{1, 2, 3} | 先造一个数组,再返回它的切片 |
| 从数组切 | s := arr[1:4] | 窗口指向已有数组 |
| make | s := make([]int, 5, 10) | 造一个长度 5、容量 10 的切片 |
make 专门用来分配切片(还有 map 和 channel):
make([]int, 长度, 容量),容量可以省略,省略时容量等于长度14。
用 make 造出来的切片,元素全是零值:make([]int, 2, 4) → [0 0],len 2, cap 415。
一个容易踩的坑书里特意点了:make 造的长度是 2 的切片,
直接写 slice1[2] = 78 会报错——第 2 格还在容量里、不在长度里,
得先让它「长」进来15。
append:唯一的生长方式
append 在切片末尾加元素,返回一个新的切片(必须接回返回值):
slice1 = append(slice1, 345, 646, 0) // 一次加多个也行
slice1 = append(slice1, slice2...) // ... 把另一个切片「拆散」加进来三个细节,原书都有16:
- 容量不够时,append 会悄悄换货架:申请一块更大的底层数组,把旧数据全搬过去, 再追加——旧的切片还指着旧货架,互不相干;
- 往 nil 切片上 append 是合法的:
var s []int是 nil,s = append(s, 45)→[45]; - 容量够的时候 append 不搬家,直接往后写——这时它和旧切片还共享着数组。
「扩容量」的手动版(老写法):造一个更大的切片、copy 过去、再赋回来;
书里也提了一句新写法:标准库 slices 包的 Grow 函数一行搞定17。
copy:按「较短的算」
n := copy(dst, src) // 把 src 的元素拷进 dst,返回实际拷了几个copy 不增长目标切片,只覆盖重叠的那一段;拷贝个数 = min(len(dst), len(src))。 目标比源长,尾部保留原样;目标比源短,装不下的不要; 目标是空的,什么都不发生(返回 0)18。
4. 切片操作工具箱
原书后半章是一组标准库操作,压缩成速查表:
| 想干什么 | 用什么 | 例子(照原书) |
|---|---|---|
| 排序(升序) | sort.Ints/Strings/Float64s | [23 0 105 58] → [0 23 58 105]19 |
| 自定义排序 | slices.SortFunc(带比较函数) | 见原文20 |
| 判等 | reflect.DeepEqual(s1, s2) | 类型+元素全同才 true21 |
| 查找 | sort.SearchInts(s, x) | 二分查找,要求切片已升序;找不到时返回「该插入的位置」:SearchInts([0 9 16 105 345], 78) → 322 |
| 反转 | sort.Sort(sort.Reverse(sort.IntSlice(s))) | [0 888 9 16 105 345] → [888 345 105 16 9 0]23 |
| 追加 | append | 见上节 |
DeepEqual 的例子值得停一下:make([]int, 4) 造出来的 [0 0 0 0],
和字面量 []int{23, 0, 105, 0} 比是不等的(元素不同),
而和「前三个元素手动设成 23,0,105 的」切片是相等的21。
SearchInts 的第二个返回值语义特别容易误用:它不一定找到了元素! 返回的是「如果要把目标插进来、该插在哪个位置」。 确认「真的存在」还得再比一下该位置的元素(补充,不在书里,来自通用知识)。
二维切片与结构体切片
二维数组 arr := [3][2]int{{1,2},{4,5},{7,8}} 是「数组的数组」,每行定长24。
二维切片 [][]int{{1,2,78,54},{0,3,4},{5,6}} 是「切片的切片」,每行可以长短不一25——
这是切片比数组灵活的一个实在场景。
切片也能装结构体:[]struct{ roll_no int; name string; present bool },
输出形如 [{1 Rahul true} {3 Akansha false}]——struct 是什么、
第 11 章专讲,这里只看到「切片能装任何同类型的东西」26。
5. 作者的判断与证据
| 书里的说法 | 性质 |
|---|---|
| 「数组的这些限制催生了切片」 | 事实+动机叙述,属实 |
| 「切片比数组强大,能做多种操作」 | 属实,但「强大」附带了共享引用的风险,书里未强调 |
| 「sort 的包总是升序排序」 | 属实;反转要靠 Reverse 包一层23 |
6. 边界与局限
- 切片之间不能用
==比较(语法禁止,只能 DeepEqual)——书里只演示了 DeepEqual, 没解释为什么;原因:切片含指针,==比较指针还是比数据没有直观答案; - append 的「容量不够就搬家」没有给具体增长策略(翻倍?1.25 倍?)—— 实现细节且随版本调整,不建议依赖(补充,不在书里);
- 删除切片中间元素的惯用写法(
append(s[:i], s[i+1:]...))原书没有讲, 属于常用补遗(补充,不在书里,来自通用知识); slices包(Go 1.21 引入)只在 Grow 一处被提到, 它的 Sort/Contains/Delete 等常用函数未展开。
7. 可带走的
- 数组:定长、索引从 0、值语义(赋值/传参 = 整份复制);
- 数组的四种初始化,
[...]让编译器数长度;零值自动补齐([1,4,7]==[1,4,7,0]); len取长度;二维数组的 len 是行数;- 切片 = 指针 + 长度 + 容量,是数组的窗口,不拥有数据;
len是窗口里的格子数,cap是窗口最多能扩到的格子数;- 改底层数组,所有切片跟着变——共享是特性,也是 bug 源;
- 出生三法:字面量、从数组切、make;nil 切片也能 append;
append必须接回返回值;容量不够时会换底层数组(旧切片不受影响);copy(dst, src)按较短的长度拷,不增长目标;- 排序用 sort.Ints/Strings/Float64s;判等用 reflect.DeepEqual; 查找用 sort.SearchInts(先排序,找不到时返回插入位置)。
8. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 数组定义:定长、值类型 | Arrays and Slices | text/16-fm-learning-go-programming.txt:79(搜「fixed length」) |
| 初始化与零值补齐 | Arrays and Slices | text/16-fm-learning-go-programming.txt:160(搜「predefined values」) · text/16-fm-learning-go-programming.txt:220(搜「0 2 0 4 0」) |
| [...] 省略号定长 | Arrays and Slices | text/16-fm-learning-go-programming.txt:232(搜「ellipsis」) |
| 数组的动机 | Arrays and Slices | text/16-fm-learning-go-programming.txt:280(搜「grow or shrink」) |
| len 与越界 | Arrays and Slices | text/16-fm-learning-go-programming.txt:379(搜「out of bound」) · text/16-fm-learning-go-programming.txt:415(搜「Length of 2D array」) |
| 数组比较(零值补齐也相等) | Arrays and Slices | text/16-fm-learning-go-programming.txt:421(搜「same type and size」) · text/16-fm-learning-go-programming.txt:484(搜「arr2 and arr5 true」) |
| 数组复制是整份拷贝 | Arrays and Slices | text/16-fm-learning-go-programming.txt:499(搜「behaves like a value」) · text/16-fm-learning-go-programming.txt:517(搜「arr2 is」) |
| 数组传参与求和 | Arrays and Slices | text/16-fm-learning-go-programming.txt:544(搜「func sum」) · text/16-fm-learning-go-programming.txt:568(搜「sum of array elements is: 14」) |
| range 遍历与空白标识符 | Arrays and Slices | text/16-fm-learning-go-programming.txt:616(搜「range arr」) · text/16-fm-learning-go-programming.txt:667(搜「blank identifier」) |
| 数组的四条限制 | Arrays and Slices | text/16-fm-learning-go-programming.txt:708(搜「invalid array length」) |
| 切片定义与 nil 切片 | Arrays and Slices | text/16-fm-learning-go-programming.txt:729(搜「segment of an array」) · text/16-fm-learning-go-programming.txt:741(搜「nil slice」) |
| 切片的切法 low:high | Arrays and Slices | text/16-fm-learning-go-programming.txt:774(搜「low indicates」) · text/16-fm-learning-go-programming.txt:822(搜「4 5 6 7」) |
| 再切片不复制、共享底层数组 | Arrays and Slices | text/16-fm-learning-go-programming.txt:837(搜「does not copy」) · text/16-fm-learning-go-programming.txt:915(搜「2 90 4 5 6 7 8」) |
| 三件套:指针/长度/容量 | Arrays and Slices | text/16-fm-learning-go-programming.txt:972(搜「pointer to an array segment」) · text/16-fm-learning-go-programming.txt:1075(搜「capacity of a slice」) |
| len=3 cap=8 的例子 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1096(搜「arrayval[2:5]」) · text/16-fm-learning-go-programming.txt:1108(搜「capacity of slice 8」) |
| make 与「长度外写入报错」 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1126(搜「capacity of the slice is optional」) · text/16-fm-learning-go-programming.txt:1156(搜「error as length」) |
| 手动扩容与 slices.Grow | Arrays and Slices | text/16-fm-learning-go-programming.txt:1168(搜「increase the capacity」) · text/16-fm-learning-go-programming.txt:1207(搜「capacity of slice1: 14」) · text/16-fm-learning-go-programming.txt:1210(搜「slices.Grow」) |
| copy 的签名与「不增长」 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1285(搜「destination, source」) · text/16-fm-learning-go-programming.txt:1456(搜「does not add any new value」) |
| sort 排序 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1462(搜「ascending order」) |
| DeepEqual 判等 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1546(搜「DeepEqual」) · text/16-fm-learning-go-programming.txt:1606(搜「slice1 equal to slice4: true」) |
| SearchInts 与插入位置 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1612(搜「binary search」) · text/16-fm-learning-go-programming.txt:1645(搜「SearchInts(slice1, 78)」) |
| 反转 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1699(搜「sort.Reverse」) |
| append 与 ... 展开 | Arrays and Slices | text/16-fm-learning-go-programming.txt:1726(搜「new slice is allocated」) · text/16-fm-learning-go-programming.txt:1801(搜「slice2...」) |
Footnotes
-
出处:「Arrays and Slices」第 79 段(
text/16-fm-learning-go-programming.txt:79,搜「fixed length」)。 ↩ -
出处:「Arrays and Slices」第 163–229 段(
text/16-fm-learning-go-programming.txt:160,搜「predefined values」;text/16-fm-learning-go-programming.txt:220,搜「0 2 0 4 0」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 232–241 段(
text/16-fm-learning-go-programming.txt:232,搜「ellipsis」)。 ↩ -
出处:「Arrays and Slices」第 247–291 段(
text/16-fm-learning-go-programming.txt:280,搜「grow or shrink」)。 ↩ -
出处:「Arrays and Slices」第 379–415 段(
text/16-fm-learning-go-programming.txt:415,搜「Length of 2D array」)。 ↩ -
出处:「Arrays and Slices」第 421–493 段(
text/16-fm-learning-go-programming.txt:484,搜「arr2 and arr5 true」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 499–517 段(
text/16-fm-learning-go-programming.txt:499,搜「behaves like a value」;text/16-fm-learning-go-programming.txt:517,搜「arr2 is」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 604–702 段(
text/16-fm-learning-go-programming.txt:616,搜「range arr」;text/16-fm-learning-go-programming.txt:667,搜「blank identifier」)。 ↩ -
出处:「Arrays and Slices」第 708 段(
text/16-fm-learning-go-programming.txt:708,搜「invalid array length」)。 ↩ -
出处:「Arrays and Slices」第 1051–1081 段(
text/16-fm-learning-go-programming.txt:1075,搜「capacity of a slice」)。 ↩ -
出处:「Arrays and Slices」第 1096–1108 段(
text/16-fm-learning-go-programming.txt:1108,搜「capacity of slice 8」)。 ↩ -
出处:「Arrays and Slices」第 774–831 段(
text/16-fm-learning-go-programming.txt:822,搜「4 5 6 7」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 837–918、988–1048 段(
text/16-fm-learning-go-programming.txt:915,搜「2 90 4 5 6 7 8」;text/16-fm-learning-go-programming.txt:1042,搜「178」)。 ↩ -
出处:「Arrays and Slices」第 1117–1132 段(
text/16-fm-learning-go-programming.txt:1126,搜「capacity of the slice is optional」)。 ↩ -
出处:「Arrays and Slices」第 1141–1162 段(
text/16-fm-learning-go-programming.txt:1156,搜「error as length」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 1726–1783 段(
text/16-fm-learning-go-programming.txt:1726,搜「new slice is allocated」;text/16-fm-learning-go-programming.txt:1783,搜「45 67」)。 ↩ -
出处:「Arrays and Slices」第 1168–1210 段(
text/16-fm-learning-go-programming.txt:1207,搜「capacity of slice1: 14」;text/16-fm-learning-go-programming.txt:1210,搜「slices.Grow」)。 ↩ -
出处:「Arrays and Slices」第 1282–1456 段(
text/16-fm-learning-go-programming.txt:1288,搜「minimum of the length」;text/16-fm-learning-go-programming.txt:1456,搜「does not add any new value」)。 ↩ -
出处:「Arrays and Slices」第 1462–1537 段(
text/16-fm-learning-go-programming.txt:1528,搜「0 23 58 105」)。 ↩ -
出处:「Arrays and Slices」第 1540 段(
text/16-fm-learning-go-programming.txt:1540,搜「SortFunc」)。 ↩ -
出处:「Arrays and Slices」第 1546–1606 段(
text/16-fm-learning-go-programming.txt:1606,搜「slice1 equal to slice4: true」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 1612–1660 段(
text/16-fm-learning-go-programming.txt:1612,搜「binary search」;text/16-fm-learning-go-programming.txt:1645,搜「SearchInts(slice1, 78)」)。 ↩ -
出处:「Arrays and Slices」第 1666–1720 段(
text/16-fm-learning-go-programming.txt:1699,搜「sort.Reverse」)。 ↩ ↩2 -
出处:「Arrays and Slices」第 295–367 段(
text/16-fm-learning-go-programming.txt:331,搜「3x2」)。 ↩ -
出处:「Arrays and Slices」第 1213–1270 段(
text/16-fm-learning-go-programming.txt:1258,搜「slice1 [[1 2 78 54]」)。 ↩ -
出处:「Arrays and Slices」第 921–963 段(
text/16-fm-learning-go-programming.txt:963,搜「Rahul」)。 ↩