今天最熱的事情,莫過於微信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/,第一時間看後續精彩文章。以爲好的話,請猛擊文章右下角「好看」,感謝支持。