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 函数用于扩展切片

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

相关推荐
阿眠17 小时前
前端面试题
前端
清风徐来QCQ17 小时前
SpringMvC
前端·javascript·vue.js
im_AMBER17 小时前
Leetcode 99 删除排序链表中的重复元素 | 合并两个链表
数据结构·笔记·学习·算法·leetcode·链表
Smoothzjc17 小时前
👉 求你了,别再裸写 fetch 做 AI 流式响应了!90% 的人都在踩这个坑
前端·人工智能·后端
沛沛老爹17 小时前
Web开发者进阶AI:Agent技能设计模式之迭代分析与上下文聚合实战
前端·人工智能·设计模式
YangYang9YangYan17 小时前
中专大数据技术专业学习数据分析的价值分析
大数据·学习·数据分析
yong999017 小时前
基于MATLAB的大变形悬臂梁求解程序
前端·数据库·matlab
Swift社区17 小时前
ArkTS Web 组件里,如何通过 javaScriptProxy 让 JS 同步调用原生方法
开发语言·前端·javascript
小和尚敲木头17 小时前
记录一次vue3中this引发的开发没有问题,生产发生问题的分析
前端·vue
testpassportcn18 小时前
Fortinet FCSS_SDW_AR-7.4 認證介紹|Fortinet Secure SD-WAN 高級路由專家考試
网络·学习·改行学it