slice全解析

來源:互聯網
上載者:User
slice全解析

昨天組內小夥伴做分享,給出了這麼一段代碼:

package mainimport (  "fmt")func fun1(x int) {  x = x + 1}func fun2(x *int) {  *x = *x + 1}func fun3(x []int) {  x = append(x, 3)}func fun4(x *[]int) {  *x = append(*x, 3)}func fun5(x [4]int) {  x[3] = 100}func fun6(x *[4]int) {  (*x)[3] = 200}// 傳值,傳指標func main() {  x1 := 10  fmt.Printf("%+v\n", x1)  fun1(x1)  fmt.Printf("%+v\n\n", x1)  fmt.Printf("%+v\n", x1)  fun2(&x1)  fmt.Printf("%+v\n\n", x1)  var x3 []int  x3 = append(x3, 0, 1, 2)  fmt.Printf("%+v\n", x3)  fun3(x3)  fmt.Printf("%+v\n\n", x3)  fmt.Printf("%+v\n", x3)  fun4(&x3)  fmt.Printf("%+v\n\n", x3)  var x4 [4]int  for i := 0; i < 4; i++ {    x4[i] = i  }  fmt.Printf("%+v\n", x4)  fun5(x4)  fmt.Printf("%+v\n\n", x4)  fmt.Printf("%+v\n", x4)  fun6(&x4)  fmt.Printf("%+v\n\n", x4)}

可以放在play上運行一下,實際輸出的是

10101011[0 1 2][0 1 2][0 1 2][0 1 2 3][0 1 2 3][0 1 2 3][0 1 2 3][0 1 2 200]

得出的結論是:slice是引用傳遞,數組是值傳遞,但是要想修改slice和數組,都需要把slice或者數組的地址傳遞進去。

這個結論中的數組是值傳遞,要在調用函數內部修改數組值,必須傳遞數組指標,我沒有什麼意見。但是slice的部分,卻並沒有那麼簡單。基本上,需要明確下面幾點才能解釋上面的代碼。

slice的結構是uintptr+len+cap

比如我定義了一個slice, 不管是什麼方法定義的

var a []inta = make([]int, 1)a := []int{1,2}

這裡的a都是由一個固定的資料結構賦值的

這個資料結構有三個,一個是指向一個定長數組的指標,一個是len,表示我這個slice包含了幾個值,還有一個是cap,表示我申請定長數組的時候申請了多大的空間。

slice的append操作是根據cap和len的關係判斷是否申請新的空間

在記憶體看空間中,沒有不定長的數組,所有不定長數組的文法都是語言本身封裝了。比如golang中的slice。slice可以在初始化的時候就定義好我需要使用多大的空間(cap)

a := make([]int, 1, 10)

這裡的10也就是cap,1是len,說明我已經建立了10個int空間給這個slice。
當不斷往a中append資料的時候,首先是len不斷增加,當len和cap一樣的時候,這個時候再append資料,就會新開闢一個數組空間,這個數組空間長度為多大呢?2*cap。
舉例說,如果上述的a後續又append了9個資料,這個時候如果再append一個資料,就會發現cap變成20了。

當然,如果擴容了,那麼我們說的slice的第一個元素,指向定長數組的地址就會變化。

理解下下面這個代碼:

package mainimport "fmt"import "unsafe"func main() {    var a []int    a = append(a, 0)    printSlice("a", a)    a = append(a, 1)    printSlice("a", a)    a = append(a, 2, 3, 4)    printSlice("a", a)}func printSlice(s string, x []int) {    fmt.Printf("%s len=%d cap=%d ptr=%p %v\n",s, len(x), cap(x), unsafe.Pointer(&x[0]), x)}       輸出:a len=1 cap=2 ptr=0x10414020 [0]a len=2 cap=2 ptr=0x10414020 [0 1]a len=5 cap=8 ptr=0x10458020 [0 1 2 3 4]
函數參數是slice的時候傳遞的是“slice結構”的值拷貝

我們說的slice為參數傳遞的時候傳遞是引用傳遞,實際上,它傳遞的是Slice結構(uintptr+len+cap)的一個複製,但是由於uintptr對應的是一個定長的數組,所以基本上當slice作為參數傳遞的時候,返回回來的slice結構是不會變的,對應的定長數組的大小是不會變的,但是這個定長數組裡面的具體值是有可能變的。

看下面幾個例子:

package mainimport (  "fmt"  "unsafe")func fun3(x []int) {  fmt.Printf("%p\n", unsafe.Pointer(&x[0]))  x = append(x, 3)  x[2] = 100  fmt.Printf("%p\n", unsafe.Pointer(&x[0]))}func main() {  var x3 []int  x3 = append(x3, 0, 1, 2)  fmt.Printf("%+v\n", x3)  fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))  fun3(x3)  fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))  fmt.Printf("%+v\n\n", x3)}輸出:[0 1 2]0x104140200x104140200x104140200x10414020[0 1 100]

這裡的x3[2]的的值變化了。但是slice的指標地址沒有變化。

如果在f3裡面修改x[3]的值:

package mainimport (  "fmt"  "unsafe")func fun3(x []int) {  fmt.Printf("%p\n", unsafe.Pointer(&x[0]))  x = append(x, 3)  x[3] = 100  fmt.Printf("%p\n", unsafe.Pointer(&x[0]))}func main() {  var x3 []int  x3 = append(x3, 0, 1, 2)  fmt.Printf("%+v\n", x3)  fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))  fun3(x3)  fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))  fmt.Printf("%+v\n\n", x3)}輸出:[0 1 2]0x104140200x104140200x104140200x10414020[0 1 2]

這裡的x3的值就不會變化,雖然不會變化,但是實際上slice指向的定長數組的索引為3的值已經變化了。

如果f3是append兩個呢?

package mainimport (  "fmt"  "unsafe")func fun3(x []int) {  fmt.Printf("%p\n", unsafe.Pointer(&x[0]))  x = append(x, 3, 4)  x[3] = 100  fmt.Printf("%p\n", unsafe.Pointer(&x[0]))}func main() {  var x3 []int  x3 = append(x3, 0, 1, 2)  fmt.Printf("%+v\n", x3)  fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))  fun3(x3)  fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))  fmt.Printf("%+v\n\n", x3)}輸出:[0 1 2]0x104140200x104140200x104580000x10414020[0 1 2]

我們看到在fun3裡面append之後的x這個slice指向的地址變化了。但是由於這個x實際上是我們傳遞進去的x3的值拷貝,所以這個x3並沒有被修改。最後輸出的時候還是沒有變化。

總結

基本上記住了這幾個點就明白了slice:

  • slice的結構是uintptr+len+cap
  • slice的append操作是根據cap和len的關係判斷是否申請新的空間
  • 函數參數是slice的時候傳遞的是“slice結構”的值拷貝
參考

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.