一文看懂 slice

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 語言中雖然有數組,但在代碼中直接用的比較少,而是會間接的用到,slice 存儲數據就是用的 數組,甚至可以認爲數組是爲了 slice 存在。Go 語言中的 slice 可以當做數組來使用,也可以當做其他語言中的 List 來使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 表示一個擁有相同類型元素的可變長的序列。可變長是 slice 最重要的一個特性,這是它和數組最不同的地方,既然 slice 是依靠數組而存在的,那麼 slice 是如何做到可變長的呢,這篇文章就來聊一下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先從 slice 的創建說起。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"1. slice 的創建","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 可以主動創建,也可從現有的數組中獲取。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"帶初始化元素的方式:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums := []int{0, 1, 2, 3, 4, 5}\nfmt.Println(len(nums)) // 6\nfmt.Println(cap(nums)) // 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不帶初始化元素的方式,下面的這種方式會自動填充零值:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums := make([]int, 6)\nfmt.Println(len(nums)) // 6\nfmt.Println(cap(nums)) // 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還可以創建一個更大容量的 slice:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums := make([]int, 6, 12)\nfmt.Println(len(nums)) // 6\nfmt.Println(cap(nums)) // 12","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏需要需要注意,雖然 slice 容量有 12,但是卻不能直接訪問 slice 長度範圍外的元素,否則會出現 panic:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"fmt.Println(nums[6]) // panic","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要麼通過擴展 slice 的長度,要麼使用 append 函數來添加元素。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums = nums[:len(nums)+1] // 將 slice 的長度擴展大 1\nnums = append(nums, 1) // 使用 append 將 nums 的長度加 1","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用上面的方式創建 slice 時,Go 會自動的創建一個底層數組來存儲數據,slice 本身並不會存儲數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一種方式是通過數組來創建 slice:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{0, 1, 2, 3, 4, 5}\n\nnumsSlice := numsArr[:] // 這表示 slice 容納數組的所有元素\nfmt.Println(len(numsSlice)) // 6\nfmt.Println(cap(numsSlice)) // 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"2. slice 的結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般 可以把 slice 看成是一個複合結構,結構如下,由三部分組成:指針、長度和容量:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type Slice struct {\n ptr *T\n len, cap int\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 本身不會存儲任何數據,slice 通過指針指向底層數組的某一個位置,這個位置就是 slice 的初始位置。長度表示當前 slice 中的元素個數,容量表示從指針的位置到底層數組的最後一個位置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 可變長的第一個原因就是可以通過改變指針的位置來改變 slice 的長度。比如下面這樣:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsSlice2 := numsSlice[1:3]\nfmt.Println(len(numsSlice2)) // 2\nfmt.Prinltn(cap(numsSlice2)) // 5","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個操作在 numsSlice 的基礎上創建的一個新的 slice,其實就是移動了一下 slice 的指針。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 slice 不能使用 == 進行比較,因爲 slice 的元素是非直接的,也就是 slice 本身不存儲值,slice 甚至可以包含自身。另外因爲 slice 的元素是靠底層的數組存儲,所以當底層數組變動的時候,slice 讀取到的值也會產生變化。如果 使用 == 來進行比較,可能會產生很多預料之外的結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"3. 理解 slice 的 cap","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於初學者來說,會把 slice 的長度和容量搞混。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來看下面這段代碼:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{1, 2, 3, 4, 5, 6}\n\ns1 := numsArr[0 : 3]\nfmt.Println(s1) // [1,2,3]\nfmt.Println(cap(s1)) // 6\nfmt.Println(len(s1)) // 3\n\ns2 := numsArr[3:5]\nfmt.Println(s2) // [4,5]\nfmt.Println(cap(s2)) // 3\nfmt.Println(len(s2)) // 2","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以發現,同樣是從數組上來生成 slice,但是生成之後的 slice 的容量卻不一樣,在上面而我們說到了 slice 的初始位置靠指針來決定,s1 指針的位置在數組的最開始的位置。按照容量的定義,從指針到底層數組結束的地方,所以它的 容量還是 6。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 s2 中,指針的位置是數組的第四個元素,長度是 3,容量也是 3。這是初學者最容易犯錯的地方。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"長度表示 slice 中目前可訪問的元素個數,容量則表示這個 slice 在不改變底層數組的情況下,最多個擴展到的長度。如果要擴展 slice,使用下面的操作就可以:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"s2 = s2[:3] // 把上面 s2 的長度從 2 擴展到 3\nfmt.Println(s2[2]) // 訪問第三個元素","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方式是讓 slice 可變長的第二種方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"4. 理解 append","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"append 方法也是讓 slice 可變長的一種方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"append 函數經常會配合 slice 一起使用,在使用 append 的時候,我們需要使用下面的語法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums = append(nums, 1)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且這種做法是必須的,如果沒有采用這種做法,可能會產生意料之外的結果。看下面的代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{1, 2, 3, 4, 5, 6}\n\ns1 := numsArr[3:5]fmt.Println(cap(s1)) // 3fmt.Println(len(s1)) // 2\n\n_ = append(s1, 1)\n\nfmt.Println(s1[2]) // panic","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代碼在調用 append 之後,如果沒有使用返回的 slice,而是直接使用原來的 slice,就會產生越界的錯誤。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲 slice 本身雖然是可變長的,所以如果 slice 還有容量,那麼每次添加元素,都需要擴展 slice 的長度,返回的也是一個新的 slice。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 slice 依賴的底層數組是固定長度,在使用 append 時,如果底層的數組不足以存儲新的元素之後,就需要擴容,擴容之後就會產生一個新的 slice 返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以理解 append 方法修改傳入的 slice,而我們在調用 append 函數之後,就需要使用函數返回的結果。所以上面的代碼需要修改爲:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{1, 2, 3, 4, 5, 6}\n\ns1 := numsArr[3:5]\nfmt.Println(cap(s1)) // 3\nfmt.Println(len(s1)) // 2\n\ns1 = append(s1, 1)\n\nfmt.Println(s1[2]) //1\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"5. 小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 是一個很有用的數據接口,既可以當數組用,也可以當 list 使用。在使用 slice 的過程中,一定要注意 slice 本身不存儲數據,它只是在一個底層數組上,截取不同長度序列,可以說 slice 就是一個數組的子序列。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外我們說到了 slice 可變長的幾種方式,一種是通過移動 slice 的指針,改變 slice 的長度,第二種是 slice 容量範圍內擴展長度,第三種是通過 append 方式,這種方式會創建一個新的 slice。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外我們經常會對 string 做子串的截取操作和這裏 slice 工作原理是一樣的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"文 / Rayjun","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文首發於微信公衆號【Rayjun】","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章