---
# System prepended metadata

title: 'Day 10 : Golang Slice'
tags: [About Go]

---

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