Saki's 研究记录

Golang数组与Slice,以及append函数的陷阱

字数统计: 634阅读时长: 2 min
2023/09/26

有时候我们会将slice当做参数传递到函数,给这个slice做一些修改的情况。想到slice是引用传递,可以直接传递slice用作修改,于是可能出现下面这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
arr := []int{1}
modifySlice(arr)
fmt.Println(arr) // [1]
}

func modifySlice(arr []int) {
arr = append(arr, 2)
arr[0] = 0
fmt.Println(arr) // [1,2]
return
}

我们利用modifySlice函数对传入的arr进行修改和添加的操作,想到slice是引用传递,在modifySlice中的修改必然会生效。
但是实际上呢,我们看一下运行情况:

1
2
3
# go run main
[1,2]
[1]

运行结果表明我们对s[0]做的修改没有生效,添加新元素的操作也未生效,这是为什么呢?
原因就在于添加新元素是用的append,并将原先的引用重新赋值了!
我们把引用当成指针来看,指针指向的内容就是slice的数据内容。
指针本身是一种变量类型,指针类型在传递时,是值传递!

也就是说我们在外面调用modifySlice时,传入arr是一个指针变量,当进入到modifySlice函数时,形参arr跟外面传入的arr并不是一个值,是arr的值拷贝!但是他们的值是一样的,即指向的数据内容都是slice中的数据。

然而我们用这种方式:arr = append(arr, 2),这是在修改指针,并不是在修改指针指向的数据!

append可能会因为arrcap不足,重新分配空间(扩容过的数组),所以append有一个返回参数。

如果append超过设定的区间,那么Slice底层就会扩容了

我们对modifySlice中的arr进行了修改,但是arr是指针,仅仅是外层传入的slice指针的拷贝,说明我们并没有对外层的slice重新赋值!
这就是添加新元素失败的原因。

我们再执行arr[0] = 0,实际上是修改的是指针指向的数据的内容(扩容过的数组)!这里肯定不会生效!

如果把函数modifySlice中的语句调换下顺序,修改为:

1
2
3
4
5
6
func modifySlice(arr []int) {
arr[0] = 0
arr = append(arr, 2)
fmt.Println(arr) // [0,2]
return
}

则运行情况则会改变:

1
2
3
# go run main
[0,2]
[0]

这里用arr[0] = 0做了修改,实际上是修改指针指向的数据的内容(未扩容的数组)!这里就会修改到外部传入的arr

以上。

CATALOG