今天最热的事情,莫过于微信7.0的发布,增长了短视频,优化了看一看等功能,原本想跟着个热度,蹭个流量,后来发现各位大佬都已经开始蹭了,就算了,仍是谈谈Go语言(golang)吧,看来要成为一个合格的自媒体,仍是不要矜持,任重道远啊。html
前两天有朋友(Weelin)在个人公众号上留言,留言的文章是这一篇 Go语言实战笔记(五)| Go 切片 ,这是一篇讲Go语言(golang) Slice(切片)的,很早的一篇文章。这位朋友的留言不是讲本身的问题,而是针对另一位朋友(Dreamerque)的留言的说明。golang
为了连贯说明问题,咱们先来看下2018-03-17,Dreamerque这位朋友的留言:数组
有个问题困扰: 考虑将slice这种引用类型做为自定义接受者,并绑定方法以下,bash
问题: 此时的slice空间容量足够,调用方法先后其地址并不会改变,那么为什么append后的切片内部成员不会改变? 默认拷贝的副本是slice引用,应该要能修改或者添加成员才符合预期的。。微信
type Slice []int
func (A Slice)Append(value int) {
A = append(A, value)
}
func main() {
mSlice := make(Slice, 10, 20)
mSlice.Append(5)
fmt.Println(mSlice)
}
复制代码
经过代码,相信你们也看明白了,以上就是Dreamerque的问题和困惑。我当时给Dreamerque的回答是引用的数据源不一致,让他参考个人 Go语言中new和make的区别 这篇文章 。app
而后就在前两天,我收到了Weelin的留言:函数
无情你好,我理解mslice的数据源应该是没发生变化的。因为值拷贝的缘由,Append方法先后的切片惟一有关联的就是底层指向的数组,打印结果不同就是由于原来切片过短了。这个也能够在执行完Append方法后,生成一个新的切片(长度大于5)并打印验证。测试
Weelin的留言更细,分析的更准,这时候,我才知道,原来我那个回答,有点误导Dreamerque了,可能会把我说的数据源理解成更底层的Data数组了。优化
从以上的输出打印中,咱们的确能够看到mSlice
并无任何变化,就是方法Append
没有起任何做用。Dreamerque的困惑是以为Slice是引用类型,修改了指向应该也会跟着改,其实咱们知道,这个修改引用的指向是在Append
方法内的,离开就不起做用了。网站
其实以上都不是根本,根本是Weelin提到的,append
后的Slice已经不是原来的Slice了。这时候有的朋友可能又疑惑了,append
返回的Slice的指针和原Slice的指针同样的啊,怎么会不是一个呢?咱们来测试一次,修改代码以下:
func (A Slice)Append(value int) {
A1 := append(A, value)
fmt.Printf("%p\n%p\n",A,A1)
}
复制代码
咱们用A1
存储append
方法返回的Slice,而后打印返回A1
和原A
的指针地址,发现的确同样。你们能够本身运行试试。其实咱们本身在make
一个Slice的时候会发现,是能够有三个参数的,一个是数据、一个是长度、一个是容量,也就是说,Slice是这样的一个结构,如今该是咱们的SliceHeader
登场的时候了。
SliceHeader是Slice运行时的具体表现,它的结构定义以下:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
复制代码
正好对应Slice的三要素,Data
指向具体的底层数据源数组,Len
表明长度,Cap
表明容量。
既然Slice就是SliceHeader,那么咱们把Slice转化为SliceHeader,来看看A
和A1
内部具体的字段值,这样来判断他们是否一致,咱们修改Append
方法以下:
//blog:www.flysnow.org
//wechat:flysnow_org
func (A Slice)Append(value int) {
A1 := append(A, value)
sh:=(*reflect.SliceHeader)(unsafe.Pointer(&A))
fmt.Printf("A Data:%d,Len:%d,Cap:%d\n",sh.Data,sh.Len,sh.Cap)
sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
fmt.Printf("A1 Data:%d,Len:%d,Cap:%d\n",sh1.Data,sh1.Len,sh1.Cap)
}
复制代码
经过unsafe.Pointer
指针进行强制类型转换,关于unsafe.Pointer
的知识能够参考个人 Go语言实战笔记(二十七)| Go unsafe Pointer 这篇文章。
都转换为*reflect.SliceHeader
类型后,咱们分别输出他们的Data
、Len
、Cap
字段,如今咱们看看输出的结果。
A Data:824634204160,Len:10,Cap:20
A1 Data:824634204160,Len:11,Cap:20
复制代码
这下你们明白了吧,他们的Len
不同,并非一个Slice,因此使用append
方法并无改变原来的A
,而是新生成了一个A1
,即便Dreamerque这位朋友经过以下代码 A = append(A, value)
进行复制,也只是一个mSlice
的拷贝A
的指向被改变了,并且这个A
只在Append
方法内有效,mSlice
自己并无改变,因此输出的mSlice
不会有任何变化。
这里正确的作法是让Append
返回append
后的结果。其实对于内置函数append
的使用,Go语言(golang)官方作了说明的,要保存返回的值。
Append returns the updated slice. It is therefore necessary to store the result of append
以上Dreamerque这位朋友的例子中,设置的Len是10,Cap是20,由于Cap足够大,因此内置函数append
并无生成新的底层数组,如今咱们把Cap改成10。
type Slice []int
func (A Slice)Append(value int) {
A1 := append(A, value)
sh:=(*reflect.SliceHeader)(unsafe.Pointer(&A))
fmt.Printf("A Data:%d,Len:%d,Cap:%d\n",sh.Data,sh.Len,sh.Cap)
sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
fmt.Printf("A1 Data:%d,Len:%d,Cap:%d\n",sh1.Data,sh1.Len,sh1.Cap)
}
func main() {
mSlice := make(Slice, 10, 10)
mSlice.Append(5)
fmt.Println(mSlice)
}
复制代码
运行代码咱们会发现两个Slice的Data
再也不同样了。
A Data:824633835680,Len:10,Cap:10
A1 Data:824634204160,Len:11,Cap:20
复制代码
这是由于在append
的时候,发现Cap
不够,生成了一个新的Data
数组,用于存储新的数据,而且同时扩充了Cap
容量。
最终,我从新回复了Dreamerque,并对Weelin作了感谢,而后想到这类问题,能够还有很多朋友会遇到,因此写了一篇文章分析下Slice的本质,也就是SliceHeader,但愿能够帮到你们,Go语言,golang ,的确够浪,SliceHeader很溜。
本文为原创文章,转载注明出处,欢迎扫码关注公众号
flysnow_org
或者网站www.flysnow.org/,第一时间看后续精彩文章。以为好的话,请猛击文章右下角「好看」,感谢支持。