【云+社区年度征文】Go语言中如何正确使用 slice
由于 Go语言中的数组长度是固定的,Go增加 slice来弥补 Go中数组的不足。在 Go的实现中 slice是对数组的部分内容的引用,这和其他语言是不一样的。在 Python和 JavaScript中变量都是引用整个数组,不存在部分引用的情况。
因为 Go中 slice的这个特性,导致在日常使用中会遇到一些问题。本文旨在列举这些情况,并提供解决方案,希望能够帮助到大家。
先看下面这个例子:
package study
import (
"fmt"
"testing"
)
func appendNum(nums []int) {
nums = append(nums, 1)
}
func TestSlice(t *testing.T) {
testSlice := make([]int, 2, 3)
fmt.Println("testSlice before appendNum: ", testSlice)
fmt.Println("testSlice[:cap(testSlice)] before appendNum: ", testSlice[:cap(testSlice)])
appendNum(testSlice)
fmt.Println("testSlice after appendNum called: ", testSlice)
fmt.Println("testSlice[:cap(testSlice)] after appendNum called: ", testSlice[:cap(testSlice)])
}
输出:
testSlice before appendNum: [0 0]
testSlice[:cap(testSlice)] before appendNum: [0 0 0]
testSlice after appendNum called: [0 0]
testSlice[:cap(testSlice)] after appendNum called: [0 0 1]
在这个例子中 testSlice做为参数传递给 appendNum函数,appendNum函数调用 append向 testSlice中添加一个元素。
按照道理来说 testSlice应该要添加一个元素,但是我们发现函数调用完成后,testSlice并没有改变,而 testSlice所引用的数组却改变了。
这是因为函数的参数是按值传递的,而 slice不仅保存了底层数组的引用,还保存了 slice所引用的范围。这就导致了上面的情况,底层数组改变了,而 slice没有改变。
解决的办法如下:
- 传递 slice的指针给函数
- 将修改后的 slice返回并重新赋值
- 最好的解决办法是不要在多个函数里修改 slice
细心的朋友可能还会提出不要在函数里修改 slice,这是因为虽然 slice是按值传递的,但是他们所引用的却是同一个数组。对 slice中的元素的修改是共享的,比如下面这个例子:
package study
import (
"fmt"
"testing"
)
func Modify(nums []int) {
nums[0] = 1
}
func TestModifySlice(t *testing.T) {
testSlice := make([]int, 2, 3)
fmt.Println("testSlice before Modify: ", testSlice)
Modify(testSlice)
fmt.Println("testSlice after Modify called: ", testSlice)
}
输出:
testSlice before Modify: [0 0]
testSlice after Modify called: [1 0]
这时可能有人很高兴,这样也不错嘛,直接在函数里修改多方便。嘿嘿,别高兴的太早,看看下面这个例子:
package study
import (
"fmt"
"testing"
)
func ModifyWithAppend(nums []int) {
nums[0] = 1
nums[1] = 2
nums = append(nums, 3)
}
func TestModifySliceWithAppend(t *testing.T) {
testSlice := make([]int, 2)
fmt.Println("testSlice before ModifyWithAppend: ", testSlice)
fmt.Println("testSlice[:cap(testSlice)] before ModifyWithAppend: ", testSlice[:cap(testSlice)])
ModifyWithAppend(testSlice)
fmt.Println("testSlice after ModifyWithAppend called: ", testSlice)
fmt.Println("testSlice[:cap(testSlice) after ModifyWithAppend called: ", testSlice[:cap(testSlice)])
}
输出:
testSlice before ModifyWithAppend: [0 0]
testSlice[:cap(testSlice)] before ModifyWithAppend: [0 0]
testSlice after ModifyWithAppend called: [1 2]
testSlice[:cap(testSlice) after ModifyWithAppend called: [1 2]
那如果确实需要在函数里修改 slice中的元素又不想影响到函数外的变量怎么办?我的解决办法是在要修改的函数里 使用 copy函数复制要修改的 slice到一个新的 slice中(底层数组不同)。
比如:
package study
import (
"fmt"
"testing"
)
func FuncWithCopy(nums []int) {
newNums := make([]int, len(nums))
copy(newNums, nums)
newNums[0] = 1
fmt.Println("newNums: ", newNums)
}
func TestFuncWithCopy(t *testing.T) {
testSlice := make([]int, 2)
fmt.Println("testSlice before FuncWithCopy: ", testSlice)
FuncWithCopy(testSlice)
fmt.Println("testSlice after FuncWithCopy called: ", testSlice)
}
输出:
testSlice before FuncWithCopy: [0 0]
newNums: [1 0]
testSlice after FuncWithCopy called: [0 0]
不过这个在使用的时候需要注意 copy不会为 newNums分配内存,所以 newNums使用 make初始化,并保证长度大于等于目标 slice。