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

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

相关推荐
EchoEcho2 小时前
记录overflow:hidden和scrollIntoView导致的页面问题
前端·css
MrSYJ2 小时前
Redis 做分布式 Session
后端·spring cloud·微服务
Cache技术分享2 小时前
318. Java Stream API - 深入理解 Java Stream 的中间 Collector —— mapping、filtering 和 fla
前端·后端
学编程的闹钟2 小时前
95【给图片添加跳转链接】
学习
竟未曾年少轻狂2 小时前
Vue3 生命周期钩子
前端·javascript·vue.js·前端框架·生命周期
TT哇2 小时前
【实习】数字营销系统 银行经理端(interact_bank)前端 Vue 移动端页面的 UI 重构与优化
java·前端·vue.js·ui
蓝帆傲亦2 小时前
Web前端跨浏览器兼容性完全指南:构建无缝用户体验的最佳实践
前端
晴殇i2 小时前
【前端缓存】localStorage 是同步还是异步的?为什么?
前端·面试
不一样的少年_2 小时前
Chrome 插件实战:如何实现“杀不死”的可靠数据上报?
前端·javascript·监控
深度涌现2 小时前
DNS详解——域名是如何解析的
前端