# 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]
}
```

(圖一,自行繪製)
## 常見操作
第一個最常見的使用方法大概就是透過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`