前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【云+社区年度征文】Go语言中如何正确使用 slice

【云+社区年度征文】Go语言中如何正确使用 slice

原创
作者头像
渔父歌
修改2020-12-18 10:27:27
4520
修改2020-12-18 10:27:27
举报
文章被收录于专栏:数据结构笔记数据结构笔记

由于 Go语言中的数组长度是固定的,Go增加 slice来弥补 Go中数组的不足。在 Go的实现中 slice是对数组的部分内容的引用,这和其他语言是不一样的。在 Python和 JavaScript中变量都是引用整个数组,不存在部分引用的情况。

因为 Go中 slice的这个特性,导致在日常使用中会遇到一些问题。本文旨在列举这些情况,并提供解决方案,希望能够帮助到大家。

先看下面这个例子:

代码语言:go
复制
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)])
}

输出:

代码语言:shell
复制
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中的元素的修改是共享的,比如下面这个例子:

代码语言:go
复制
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)
}

输出:

代码语言:shell
复制
testSlice before Modify:  [0 0]
testSlice after Modify called:  [1 0]

这时可能有人很高兴,这样也不错嘛,直接在函数里修改多方便。嘿嘿,别高兴的太早,看看下面这个例子:

代码语言:go
复制
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)])
}

输出:

代码语言:shell
复制
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中(底层数组不同)。

比如:

代码语言:go
复制
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)
}

输出:

代码语言:shell
复制
testSlice before FuncWithCopy:  [0 0]
newNums:  [1 0]
testSlice after FuncWithCopy called:  [0 0]

不过这个在使用的时候需要注意 copy不会为 newNums分配内存,所以 newNums使用 make初始化,并保证长度大于等于目标 slice。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档