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

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

相关推荐
叠叠乐2 小时前
robot_state_publisher 参数
java·前端·算法
WizLC2 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
小张快跑。2 小时前
【Java企业级开发】(十一)企业级Web应用程序Servlet框架的使用(上)
java·前端·servlet
Victor3562 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法2 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
小白阿龙2 小时前
Flex布局子元素无法垂直居中
前端
秋田君2 小时前
前端工程化部署入门:Windows + Nginx 实现多项目独立托管与跨域解决方案
前端·windows·nginx
江城开朗的豌豆3 小时前
阿里邮件下载器使用说明
前端
白宇横流学长3 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端