acongm
Go 从零学习

Go 数组与切片入门:append、copy 与底层共享

上一课我们已经学会了把一段逻辑封装成函数。接下来要解决的新问题是:如果不是处理一个值,而是要连续保存一组数据,该怎么写? 这就是数组和切片要解决的事情。

这一课只聚焦 syllabus 中的三个重点:先分清数组和切片的区别,再理解切片为什么“看起来像数组、其实更灵活”,最后学会在修改数据时正确使用 appendcopy,避免零基础阶段最容易踩到的“共享底层数据”问题。

学习目标

  • 区分数组和切片,知道它们各自适合什么场景
  • 理解切片的长度 len、容量 cap 与底层数组之间的关系
  • 学会使用 append 追加元素、使用 copy 复制数据
  • 建立“切片可能共享底层数据”的直觉,避免修改时影响原始数据

核心概念

1. 数组:长度固定的一组同类型数据

数组可以理解成“长度在创建时就定好的盒子排成一排”。

例如:

var scores [3]int

这表示:

  • scores 里只能放 int
  • 长度固定为 3
  • 创建后长度不能改成 45

你也可以直接给它初始值:

scores := [3]int{90, 85, 88}

数组的一个重要特点是:长度也是类型的一部分。

也就是说,下面两个类型并不一样:

  • [3]int
  • [4]int

这也是为什么 Go 日常开发里,数组通常更多出现在“长度非常明确”的场景;而更常用的数据结构,是下一节要重点学的切片。

2. 切片:更灵活的“视图”

切片可以理解成“对一段连续数据的可变长度访问方式”。

最常见的写法是:

names := []string{"Go", "Python", "Rust"}

注意这里和数组的区别:

  • 数组写法是 [3]string
  • 切片写法是 []string

切片没有把长度写进类型里,所以它更适合日常编程。

你可以先记住一个实用结论:

数组更像“固定大小的容器”,切片更像“真正拿来日常处理一组数据的工具”。

3. lencap 分别表示什么

切片有两个很重要的指标:

  • len:当前切片里有多少个元素
  • cap:从当前起点开始,到底层数组末尾还能容纳多少个元素

例如:

numbers := []int{10, 20, 30}
fmt.Println(len(numbers))
fmt.Println(cap(numbers))

在这个简单例子里,len(numbers)3cap(numbers) 通常也会是 3,因为它目前正好引用了这一整段数据。

零基础阶段不用死记容量扩容规则,但要先知道:切片追加元素时,可能继续使用原来的底层数组,也可能创建一块新的更大空间。

4. append:向切片尾部追加元素

Go 里最常见的切片操作就是追加数据:

numbers := []int{1, 2, 3}
numbers = append(numbers, 4)

这里有个很重要的小细节:

numbers = append(numbers, 4)

不是只写 append(numbers, 4),而是要把结果重新接住。因为 append 返回的是“追加后的新切片”。

也可以一次追加多个元素:

numbers = append(numbers, 5, 6)

5. 切片为什么会“共享底层数据”

这是今天最关键的概念之一。

看下面这个例子:

base := []int{10, 20, 30, 40}
part := base[1:3]

这时:

  • base[10 20 30 40]
  • part 会看到 [20 30]

part 并不是完全独立的新数组,它通常还是指向 base 的那块底层数据。所以如果你修改 part

part[0] = 999

那么 base 里对应位置也会变。

这就是“切片底层共享机制”的第一层直觉:

切片经常不是“拷贝出一份全新数据”,而是“借用原来那份数据的一部分”。

所以在你只想“查看一段内容”时,这很高效;但如果你想“复制一份,改副本不影响原件”,就要用 copy

6. copy:复制出独立数据

copy 的作用是把一个切片中的元素复制到另一个切片。

src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)

这样 dst 就拿到了一份独立的数据副本。之后如果你修改 dst

dst[0] = 100

src 不会跟着变化。

这在实际开发中非常重要,因为很多 bug 都来自“本来想改副本,结果把原数据也改掉了”。

7. 什么时候该用数组,什么时候该用切片

零基础阶段可以先按这个简单原则理解:

  • 如果长度固定,而且你很明确就是 7 天、12 个月、26 个字母这类固定结构,可以考虑数组
  • 如果是一般业务数据,特别是元素数量可能增加、减少、截取、拼接,优先用切片

大多数入门代码和实际项目代码里,你看到切片的频率通常会远高于数组。

代码示例

下面这个程序把“数组”“切片”“append”“copy”“共享底层数据”放到一个可运行例子里。你可以先在空目录执行:

go mod init lesson05

然后创建 main.go,写入以下内容,再运行 go run .

package main

import "fmt"

func main() {
    weekScores := [3]int{80, 85, 90}
    fmt.Println("数组 weekScores:", weekScores)

    lessons := []string{"变量", "控制流", "函数"}
    fmt.Println("原始切片:", lessons)
    fmt.Println("len:", len(lessons), "cap:", cap(lessons))

    lessons = append(lessons, "切片")
    fmt.Println("append 后:", lessons)
    fmt.Println("append 后 len:", len(lessons), "cap:", cap(lessons))

    base := []int{10, 20, 30, 40}
    part := base[1:3]
    fmt.Println("修改前 base:", base)
    fmt.Println("修改前 part:", part)

    part[0] = 999
    fmt.Println("修改后 base:", base)
    fmt.Println("修改后 part:", part)

    safeCopy := make([]int, len(base))
    copy(safeCopy, base)
    safeCopy[1] = 555

    fmt.Println("复制后的 safeCopy:", safeCopy)
    fmt.Println("原始 base 保持不变:", base)
}

如果运行成功,你应该重点观察三件事:

  1. 数组 weekScores 的长度是固定的,而切片 lessons 可以继续 append
  2. part := base[1:3] 修改后,base 对应位置也跟着变化,说明它们共享底层数据
  3. copy 得到 safeCopy 后,再修改副本时,原始 base 不会被影响

练习

  1. 新建一个长度为 4 的整数数组,保存四次练习得分并打印出来。
  2. 创建一个字符串切片,保存你已经学过的 Go 主题,然后用 append 再加入今天的“数组与切片”。
  3. 写一个切片 numbers := []int{1, 2, 3, 4},取出 part := numbers[1:3],修改 part[0],观察原切片是否变化。
  4. 使用 makecopy 复制一个切片,再分别修改原切片和副本,确认它们是否还会互相影响。
  5. 用自己的话解释:为什么 append 后通常要写成 slice = append(slice, value)

小结

今天你已经从“处理单个变量”迈向了“处理一组数据”的关键一步:知道了数组长度固定、切片更灵活,也理解了 append 用来追加元素、copy 用来复制独立数据。更重要的是,你建立了一个很有价值的直觉:切片经常共享底层数据,所以修改一个切片,可能会影响另一个切片。 下一课我们会继续学习 map 与集合操作,让程序不仅能保存“按顺序排列的数据”,也能按“键和值”快速查找信息。

On this page