ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

BUG日记:Go的切片引用传递?

2021-09-11 19:34:31  阅读:169  来源: 互联网

标签:string 容量 fmt 切片 append Go BUG change


BUG日记:Go的切片引用传递?

今天在写代码中遇到了一个问题,当我想使用函数来向切片中追加元素时发现无效。

需求为:遍历一棵路由树,将匹配节点的处理函数添加进一个slice里,最终返回,因为需要递归,所以想的传一个切片的参数,每匹配一个的时候就加入这个列表

最后由上级的函数来同一返回这个列表。

在我印象中,切片是引用传递,数组是值传递,按道理来说,只要对传进来的这个切片进行追加,最终所有结果都应该在这里面。但后面发现,递归函数没出问题,但递归结束后,切片为空,一个元素也没有。

我查了下,切片确实是引用传递,奇怪了,于是开始自己测试。

如以下代码:

func main()  {
	s := []string{}
	s = append(s, "1", "2", "3")
	change(s)
    fmt.Println(s)
}

func change(s []string)  {
	s[0] = "4"
}

由于切片是引用传递,所以结果应该是[4 2 3],程序跑出来确实如此,证明确实是引用传递,切片s中的值确实被改变了

我又换了改变s的方式,向s中追加元素,将change函数更改为

func change(s []string)  {
	s = append(s, "4")
}

但结果没有任何变化,这个4没有成功的被追加进s中,在这里,切片是引用传递的的话,就不应该没变化

于是我想到了看下s的地址

func main()  {
	s := []string{}
    fmt.Printf("%p : %v\n", s, s)
	s = append(s, "1", "2", "3")
	change(s)
    fmt.Printf("%p : %v\n", s, s)
}

func change(s []string)  {
	s = append(s, "4")
    fmt.Printf("%p : %v\n", s, s)
}

结果如下

0xb572f8 : []
0xc00004e060 : [1 2 3 4]
0xc000078480 : [1 2 3]

第一行为空切片时的地址,第二行为在change函数里查看的s的地址和值,最后一行为执行了change函数后s的地址和值

可以看出,在执行append后,引用的地址变了,我查了下资料,发现这个很好理解。

Slice切片底层结构如下

type slice struct {
  array unsafe.Pointer
  len   int
  cap   int
}

len为当前切片的长度,cap为整个切片的容量,当len == cap时在此进行append追加,切片会进行自动扩容,切片也重新指向了另外一个相关数组

切片扩容策略:

  • 如果期望容量大于当前容量的两倍就会使用期望容量
  • 如果当前切片的长度小于 1024 就会将容量翻倍
  • 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量

照这样的思路:

  1. 初始化时,切片s的len和cap都为0
  2. 第一次append,添加了三个元素,容量不足,需要扩容,期望容量为3 > 2*0,所以按照期望容量扩容,append后len和cap都为3
  3. 第二次append,添加了一个元素,容量再次不足,需要扩容,期望容量为4 , 4 < 3*2=6,按照两倍扩容,append后len为4,cap为6

可以验证以下,结果确实如此。

既然是扩容引起的指针变化,那我们是不是把初始容量设大一点就应该没问题了?

func main()  {
	s := make([]string, 0, 10)
    fmt.Printf("%p : %v\n", s, s)
	s = append(s, "1", "2", "3")
	change(s)
    fmt.Printf("%p : %v\n", s, s)
}

func change(s []string)  {
	s = append(s, "4")
    fmt.Printf("%p : %v\n", s, s)
}

如上,我新建了一个容量为10的切片,运行结果如下

0xc0000a2140 : []
0xc0000a2140 : [1 2 3 4]
0xc0000a2140 : [1 2 3]

可以看到,确实,在append过后,地址没变,但奇怪的是,尽管地址始终没变,但元素4还是没添加得进去

通过查询资料,在文章Go 切片绕坑指南 | Go 技术论坛 (learnku.com)中找到了类似的问题

通过这篇文章的解答,我有了一点自己的想法。

切片传值确实是传的引用,但传的不是真正的引用。

type slice struct {
  array unsafe.Pointer
  len   int
  cap   int
}

我认为传的是slice里面的,array这个数组的地址,这样也就可以解释。为什么我们能在另一个函数中通过赋值来改变原切片中的值,但不能通过append来为原切片追加元素,因为只传了array进来,len和cap是不变的,我们在末尾追加了元素对外界来说,也是不可见的。因为对于切片slice来说,只有前len个元素是有效的。

怎么验证我这种想法呢?我想的是,既然函数传参,Go只给你传进来了array,我就想办法把整个slice都传进来。解决办法就是:指针。把s取指传给change函数,函数来对这个指针进行操作。代码如下:

func main()  {
	s := make([]string, 0, 10)
    fmt.Printf("%p : %v\n", s, s)
	s = append(s, "1", "2", "3")
	change(&s)
    fmt.Printf("%p : %v\n", s, s)
}

func change(s *[]string)  {
	*s = append(*s, "4")
    fmt.Printf("%p : %v\n", *s, *s)
}

结果如我设想的一样,这样就是对的了,可以看到,4成功添加了进去。但我不太确定这样的做法是否正确或者说是合理,只是目前通过这样来解决了我的问题

0xc0000640a0 : []
0xc0000640a0 : [1 2 3 4]
0xc0000640a0 : [1 2 3 4]

未完待续...

以上是我目前知识范围能想到的最合理的解释,并不一定正确,后续会在进一步的学习验证后来补充这篇文章

标签:string,容量,fmt,切片,append,Go,BUG,change
来源: https://www.cnblogs.com/foresthe/p/15256542.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有