Go语言slice的本质-SliceHeader

今天最热的事情,莫过于微信7.0的发布,增加了短视频,优化了看一看等功能,本来想跟着个热度,蹭个流量,后来发现各位大佬都已经开始蹭了,就算了,还是谈谈Go语言(golang)吧,看来要成为一个合格的自媒体,还是不要矜持,任重道远啊。

留言起因

为了连贯说明问题,我们先来看下2018-03-17,Dreamerque这位朋友的留言:

有个问题困扰:

考虑将slice这种引用类型作为自定义接受者,并绑定方法如下,

问题: 此时的slice空间容量足够,调用方法前后其地址并不会改变,那么为何append后的切片内部成员不会改变? 默认拷贝的副本是slice引用,应该要能修改或者添加成员才符合预期的。。

通过代码,相信大家也看明白了,以上就是Dreamerque的问题和困惑。我当时给Dreamerque的回答是引用的数据源不一致,让他参考我的Go语言中new和make的区别这篇文章 。

然后就在前两天,我收到了Weelin的留言:

无情你好,我理解mslice的数据源应该是没发生变化的。由于值拷贝的原因,Append方法前后的切片唯一有关联的就是底层指向的数组,打印结果不一样就是因为原来切片太短了。这个也可以在执行完Append方法后,生成一个新的切片(长度大于5)并打印验证。

Weelin的留言更细,分析的更准,这时候,我才知道,原来我那个回答,有点误导Dreamerque了,可能会把我说的数据源理解成更底层的Data数组了。

问题分析

从以上的输出打印中,我们的确可以看到并没有任何变化,就是方法没有起任何作用。Dreamerque的困惑是觉得Slice是引用类型,修改了指向应该也会跟着改,其实我们知道,这个修改引用的指向是在方法内的,离开就不起作用了。

其实以上都不是根本,根本是Weelin提到的,后的Slice已经不是原来的Slice了。这时候有的朋友可能又疑惑了,返回的Slice的指针和原Slice的指针一样的啊,怎么会不是一个呢?我们来测试一次,修改代码如下:

我们用存储方法返回的Slice,然后打印返回和原的指针地址,发现的确一样。大家可以自己运行试试。其实我们自己在一个Slice的时候会发现,是可以有三个参数的,一个是数据、一个是长度、一个是容量,也就是说,Slice是这样的一个结构,现在该是我们的登场的时候了。

SliceHeader登场

SliceHeader是Slice运行时的具体表现,它的结构定义如下:

正好对应Slice的三要素,指向具体的底层数据源数组,代表长度,代表容量。

既然Slice就是SliceHeader,那么我们把Slice转化为SliceHeader,来看看和内部具体的字段值,这样来判断他们是否一致,我们修改方法如下:

通过指针进行强制类型转换,关于的知识可以参考我的Go语言实战笔记(二十七)| Go unsafe Pointer这篇文章。

都转换为类型后,我们分别输出他们的、、字段,现在我们看看输出的结果。

这下大家明白了吧,他们的不一样,并不是一个Slice,所以使用方法并没有改变原来的,而是新生成了一个,即使Dreamerque这位朋友通过如下代码 进行复制,也只是一个的拷贝的指向被改变了,而且这个只在方法内有效,本身并没有改变,所以输出的不会有任何变化。

这里正确的做法是让返回后的结果。其实对于内置函数的使用,Go语言(golang)官方做了说明的,要保存返回的值。

Append returns the updated slice. It is therefore necessary to store the result of append

以上Dreamerque这位朋友的例子中,设置的Len是10,Cap是20,因为Cap足够大,所以内置函数并没有生成新的底层数组,现在我们把Cap改为10。

运行代码我们会发现两个Slice的不再一样了。

这是因为在的时候,发现不够,生成了一个新的数组,用于存储新的数据,并且同时扩充了容量。

小结

最终,我重新回复了Dreamerque,并对Weelin做了感谢,然后想到这类问题,可以还有不少朋友会遇到,所以写了一篇文章分析下Slice的本质,也就是SliceHeader,希望可以帮到大家,Go语言,golang ,的确够浪,SliceHeader很溜。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181222B08TUB00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券