Go语言中常见100问题-#25 append操作陷阱与解决方法

前言

append函数是Go语言中操作切片高频函数,但是append函数如果使用不当在某些情况下将会产生意料之外的结果,本文将通过案例详细分析问题原因及解决方法。

案例引入

下面来看一个具体的例子,先初始化一个切片s1,基于s1创建切片s2,最后对s2执行append操作创建切片s3.

golang 复制代码
s1 := []int{1, 2, 3}
s2 := s1[1:2]
s3 := append(s2, 10)

运行上述程序后,你知道s1、s2和s3的状态是什么样的吗?先不要看下面的分析,看看自己是否答对。

原因分析

下面通过图解的形式进行说明,在执行第二条语句后,s1和s2在内存中的状态如下图所示。

s1是一个长度为3,容量为3的切片,s2是一个长度为1,容量为2的切片,s1和s2底层共用同一个数组。通过append向切片里面添加元素的时候,会检查切片是否满了(length==capacity). 如果没有满,则底层数组的下一个索引位置添加元素,并将切片的长度加1. 在本例中,切片s2还未满,所以对它执行append操作后,s3在内存中的状态如下。

底层数组中的最后一个元素的值被更新为10,打印s1、s2和s3得到输出内容如下。切片s1的内容被更新了,尽管我们没有直接对s1[2]或者s2[1]进行赋值操作。需要牢记这种操作可能引发意料之外的结果。

console 复制代码
s1=[1 2 10], s2=[2], s3=[2 10]

切片传给函数后进行append操作会引发什么问题?

下面来看将切片传递给函数产生的效果。切片s有3个元素,然后截取前2个值传给函数f. 如果在函数f中更新s的值,main函数中也会感知到s的变化。

golang 复制代码
func main() {
    s := []int{1, 2, 3}
    f(s[:2])
    // Use s
}

func f(s []int) {
    // Update s
}

如果在函数f中执行append操作,将会更新s中的第三个元素,尽管传递给f的只是前两个值。例如下面的程序,main函数中打印s的值,输出的是 [1 2 10]

golang 复制代码
func main() {
    s := []int{1, 2, 3}
    f(s[:2])
    fmt.Println(s) // [1 2 10]
}

func f(s []int) {
    _ = append(s, 10)
}

如果我们想防御性保护第三个元素不被函数f修改,有两种处理方法。

解决方法1:采用深拷贝

传递给函数f的切片与main函数中的切片彻底隔离。实现代码如下。sCopy切片是对切片s的深拷贝。

golang 复制代码
func main() {
    s := []int{1, 2, 3}
    sCopy := make([]int, 2)
    copy(sCopy, s)
    f(sCopy)
    result := append(sCopy, s[2])
    // Use result
}

func f(s []int) {
    // Update s
}

在函数f内部对s进行操作不会影响到main函数中的切片s,因为它们是完全不同的两个切片。这种方法的缺点是代码比较复杂阅读较困难,并且需要进行深拷贝操作,当要拷贝的切片很大时是一个问题。

解决方法2:限制切片的容量

采用所谓的完整切片表达式(s[low:high:max]). 该表达式与创建切片 s[low:high]类似,唯一的不同是设置切片的容量为 max-low. 具体的实现如下:

golang 复制代码
func main() {
    s := []int{1, 2, 3}
    f(s[:2:2])
    // Use s
}

func f(s []int) {
    // Update s
}

传递给函数f的切片不是s[:2]而是s[:2:2],容量为2(2-0),在内存中的状态如下图。当在函数f中向里面append元素时,因为切片已满会进行扩容操作,在新的切片上添加元素而不会影响main函数。

总结

在使用切片的时候,要留意不当操作可能引发的副作用。向切片里面添加元素时,如果切片未满,append操作会修改原切片。如果不想对原切片有影响,可以采用上面深拷贝或完整表达式的方法阻止副作用的产生。

相关推荐
想用offer打牌29 分钟前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX2 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法3 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端