Range循环和切片

今天我们继续讨论 切片(slice)。在本节中,我们将掌握:

  • 如何复制切片

  • 如何向切片追加元素

  • 如何使用 range 关键字遍历切片或数组


使用 range 遍历数组和切片

首先,假设我们有一个切片:

go 复制代码
var s = []int{0, 10, -3, 5, 99}

代码说明:

声明了一个 int 类型的切片 s,包含 5 个元素。


使用普通 for 循环遍历切片

go 复制代码
for index := 0; index < len(s); index++ {
    // 处理切片元素
}

代码说明:

  • 使用索引 index0 遍历到 len(s)-1

  • 通过 s[index] 访问每个元素


使用 for range 循环

go 复制代码
for index := range s {
    // 处理切片元素
}

代码说明:

  • range s 会自动遍历切片的索引

  • 逻辑上与上一个 for 循环等价

  • 代码更简洁、可读性更好


同时获取索引和值

go 复制代码
for index, element := range s {
    // 处理切片元素
}

代码说明:

  • index:当前元素的索引

  • element:当前元素的 副本(copy)


只需要元素值,不需要索引

go 复制代码
for _, element := range s {
    // 处理切片元素
}

代码说明:

  • 使用 _ 忽略索引

  • 如果声明了但未使用索引变量,Go 编译器会报错


range 中的元素是副本(只读)

go 复制代码
for _, element := range s {
    element = newValue   // 这个赋值不会生效
}                        // 原切片中的元素不会改变

代码说明:

  • element 是切片元素的拷贝

  • 修改它不会影响原切片


正确修改切片元素的方式

go 复制代码
for index := range s {
    s[index] = newValue
}

代码说明:

  • 通过索引直接访问并修改切片中的元素

  • 这是修改切片内容的正确方法


copy 函数

Go 提供了一个内置函数 copy 用来复制切片:

go 复制代码
copy(destination, source)
  • 第一个参数:目标切片

  • 第二个参数:源切片

  • 返回值:成功复制的元素个数

除了一个特殊情况(字符串),copy 只能用于切片

字符串的情况会在后续章节介绍


copy 示例

go 复制代码
var s = []int{12, 23, 34}
var sn = make([]int, len(s))

var n = copy(sn, s)  // n 为复制的元素个数

sn[0] = 0
sn[1] = 11

fmt.Println(n)  // 3
fmt.Println(s)  // [12 23 34]
fmt.Println(sn) // [0 11 34]

代码说明:

  • sns 的真正副本

  • 修改 sn 不会影响 s

  • n 等于 len(s),即 3


目标切片长度为 0 的情况

go 复制代码
var s = []int{12, 23, 34}
var sn []int

var n = copy(sn, s)

fmt.Println(n)  // 0
fmt.Println(sn) // []

代码说明:

  • sn 的长度为 0,没有可用空间

  • copy 不会复制任何元素


不关心返回值时

go 复制代码
copy(sn, s)

代码说明:

  • 如果复制的数量不重要,可以直接忽略返回值

append 函数

切片最重要的特性是 长度可变

Go 使用内置函数 append 向切片末尾追加元素。


向切片追加元素

go 复制代码
var s = []int{12, 23, 34}
s = append(s, 45)
fmt.Println(s) // [12 23 34 45]

s = append(s, 56, 67)
fmt.Println(s) // [12 23 34 45 56 67]

代码说明:

  • append 可以一次添加一个或多个元素

  • 它会返回一个新的切片,必须接收返回值


追加一个切片到另一个切片

go 复制代码
var s1 = []int{12, 23, 34}
var s2 = []int{45, 56, 67}

var s = append(s1, s2...)

代码说明:

  • ... 表示将 s2 拆分为多个参数

  • 可以把一个切片整体追加到另一个切片中


等价写法(仅用于理解):

go 复制代码
var s = append(s1, s2[0], s2[1], s2[2])

说明:

  • 实际中切片长度不固定,因此必须使用 ...

向 nil 切片追加元素

go 复制代码
var s []int
s = append(s, 10)

代码说明:

  • append 可以自动初始化一个 nil 切片

  • 最终 s[10]


切片的分配与重新分配(allocation & reallocation)

当切片容量不足时,append 会:

  1. 创建一个更大的底层数组

  2. 将原数据复制过去

为了减少这种开销,提前分配足够的容量是一个好习惯


预先知道容量的情况

go 复制代码
var a = []int{1, 2, 4, 3, 6}
var b = []int{-1, 9, -90}

var s = make([]int, 0, len(a)+len(b))
s = append(s, a...)
s = append(s, b...)

代码说明:

  • 提前设置好容量,避免多次重新分配

根据经验预估容量

go 复制代码
var countries = make([]string, 0, 2000)

代码说明:

  • 长度为 0,容量为 2000

  • 适合大量 append 操作


只指定长度的情况

go 复制代码
var countries = make([]string, 2000)
countries = append(countries, "Indonesia")

fmt.Println("cap:", cap(countries)) // cap: 3072

代码说明:

  • 初始 len = cap = 2000

  • 一旦 append,容量会自动扩展(重新分配)


直接通过索引赋值不会扩容

go 复制代码
var countries = make([]string, 2000)
for i := range countries {
    countries[i] = "Indonesia"
}

fmt.Println("cap:", cap(countries)) // cap: 2000

代码说明:

  • 没有使用 append

  • 容量保持不变


总结

  • 使用 for range 遍历数组和切片

  • range 中的元素是副本,不能直接修改

  • copy 函数用于复制切片

  • append 函数用于扩展切片

  • 切片的容量、重新分配以及性能优化技巧

相关推荐
yume_sibai5 小时前
大屏数据可视化 - 边框红绿呼吸灯实现详解
前端·信息可视化·typescript
冰可乐配5 小时前
fmpeg音频编码组件aac(Advanced Audio Coding (AAC) encoder)学习
学习·音视频·aac
Hyyy5 小时前
很多Desktop都在上的Computer Use是什么
前端·llm
慢功夫5 小时前
开篇:VS Code 为什么不是一个普通 React App
前端·visual studio code
憧憬成为java架构高手的小白5 小时前
MySQl学习
学习
你挚爱的强哥5 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
程序员张35 小时前
SpringBoot集成BCrypt密码加密库
java·spring boot·后端
CaffeinePro5 小时前
FastAPI数据库集成SQLAlchemy异步ORM全方案
后端·fastapi
小旭Coding5 小时前
凌晨告警轰炸!Go 服务协程只增不减,内存持续暴涨直至 OOM
后端
半个落月5 小时前
用 LangChain.js 手写一个能读写文件和执行命令的 Mini Cursor
javascript·人工智能·后端