# Day 10 : Golang Slice ## 前言 Array很方便,但因為有長度的限制,如有需要不能隨時增加會減少元素,因此在Go裡面要處裡有序的集合時,更常用的其實是slice(相較於array,slice更像是JS的array),可以隨時增加或式刪除元素,使用的方式大多和array一樣,只能允許單一型別、可以透過index讀取元素、可以透過for迴圈進行iterate。 ## Slice internals :::info A slice is a descriptor of an array segment. ::: slice 可以透過內建的make函式做宣告,當使用make來建立slice,可以指定其length和capacity,函式定義如下。 `func make([]T, len, cap) []T` 下面程式碼宣告一個s的slice,其長度為5,即目前有5個元素;容量也為5,即這個slice可以容納5個元素。但實際上如果設定capacity,可能會限制slice可以擴大的範圍,因此通常只會指定slice的長度,讓Go自動計算其cap。 ```go= func main() { // s := make([]byte, 5, 5) s := make([]int, 5) fmt.Println(s) // [0 0 0 0 0] } ``` 那為什麼會突然迸出個len和cap屬性呢? 其實slice底層是一個array segment,其由三個部分組成 * pointer : 指向真正存放數值array的起始位置 * length : slice中元素的數目,不會大於capacity * capacity : slice為一開始的位置到底層array結尾的位置 接下來分別透過程式碼與圖片進行說明。 當我們建立一個array後,透過"slicing"建立一個slice(第11行,建立slice的過程稱為slicing),當我們使用slice對原始的array進行操作的時候,會建立一個新的slice value指向array,這讓操作slice和array一樣有效率,在slice裡面做修改也會修改到底層的array,也代表是用有一樣的地址。 ```go= package main import "fmt" func main() { array := [5]int{10, 23, 66, 77, 89} fmt.Printf("array address: %p\n", &array) // array address: 0xc00000e420 fmt.Printf("The address of the second element in the array: %p\n", &array[1]) // The address of the second element in the array: 0xc00000e428 slice := array[1:4] fmt.Println(slice) // [23 66 77] fmt.Println(len(slice)) // 3 fmt.Println(cap(slice)) // 4 fmt.Printf("slice address: %p\n", &slice) // slice address: 0xc000008078 fmt.Printf("The first slice data address: %p\n", &slice[0]) // The first slice data address: 0xc00000e428 slice[0] = 99 fmt.Println(array) // [10 99 66 77 89] } ``` ![](https://i.imgur.com/XwJEiXC.png) (圖一,自行繪製) ## 常見操作 第一個最常見的使用方法大概就是透過append增加slice裡面的元素,以及透過for range進行迭代。 ```go! func main(){ s := []string{"dylan","curry"} fmt.Println(s) //[dylan curry] s = append(s, "kobe") fmt.Println(s) // [dylan curry kobe] for _,name := range s{ // index因為不需要用到,透過_省略 fmt.Println(name) } } // dylan // curry // kobe ``` 可以透過...接受任意的字串,下面註解掉的第1到第8行會跟第10到第17行產生一樣的結果 ```go= // func main(){ // addUser([]string{"bob","dylan"}) // } // func addUser(users []string){ // for _,user :=range users{ // fmt.Println(user) // } // } func main(){ addUser("bob","dylan") } func addUser(users ...string){ //可以接受任意長度的字串 for _,user :=range users{ fmt.Println(user) } } ``` 傳送任意數量的字串參數,不用一直呼叫函式 ```go= var users []string func main(){ addUser("tony","dylan") fmt.Println(users) } func addUser(user ...string){ users = append(users, user...) } ``` 當我們要刪除元素的時候,golang沒有刪除的方法,可以透過最一開始介紹,新增元素時使用到的append函式,取得兩個slice並重組成一個新的slice。而創建一個新的slice,而不是修改原始的 slice,是因為在 Go 語言中,slice是一個引用類型(reference type),當你修改一個切片時,其他引用該slice的變量也會受到影響。因此,為了避免這種情況發生,可以使用 append 函數來創建一個新的slice,然後返回這個新的slice作為結果。這樣可以保證原始的slice不會被修改,同時還可以得到預期的結果。 這邊的`...`不代表接受任意數量的元素,這比較像是個展開運算符號(ellipsis operator),將 `slice[index+1:]` 這個slice中的所有元素展開,然後作為參數傳遞給append使用,可參考第18到第23行的例子。 ```go= func main(){ numberList := []int{1,2,3,4,5} fmt.Println(rmFromSlice(numberList,1)) // [1 5 3 4] fmt.Println(rmFromSliceWithOrder(numberList,1)) // [1 3 4 5] } func rmFromSliceWithOrder(slice []int, index int)[]int{ result := append(slice[:index],slice[index+1:]...) return result } func rmFromSlice(slice []int, index int) []int{ slice[index] = slice[len(slice)-1] return slice[:len(slice)-1] } // 簡單的例子 s := []int{4, 5, 6} s = append(s, 1, 2, 3) s := []int{4, 5, 6} s = append(s, []int{1, 2, 3}...) // 等效於L19 fmt.Println(s) // [4 5 6 1 2 3] ``` ## Reference 1. [Golang Slice Tricks Every Beginner Should Know](https://www.youtube.com/watch?v=AL_C9nF_0ss) 2. https://go.dev/blog/slices-intro 3. [Go語言聖經 - slice](https://wizardforcel.gitbooks.io/gopl-zh/content/ch4/ch4-02.html) 4. https://yushuanhsieh.github.io/post/2021-12-29-golang-slice-append/ ###### tags: `About Go`