切片(slice)是對數組的一個連續片段的引用,所以切片是一個引用類型,這個片段可以是整個數組,也可以是由起始和終止索引標識的一些項的子集,需要注意的是,終止索引標識的項不包括在切片內。
## 數組生成切片
切片默認指向一段連續內存區域,可以是數組,也可以是切片本身。
從連續內存區域生成切片是常見的操作,格式如下:
`slice [開始位置 : 結束位置]`,切片結果不包括結束位置
從數組生成切片:
```
package main
import "fmt"
func main() {
var a = [5]int{1, 2, 3, 4, 5}
fmt.Println(a[1:3]) // [2 3]
}
```
從數組或切片生成新的切片擁有如下特性:
- 切片位置為:0 - len(數組);
- 取出元素不包含結束位置對應的索引;
- 當缺省開始位置時,表示從連續區域開頭到結束位置;
- 當缺省結束位置時,表示從開始位置到整個連續區域末尾;
- 兩者同時缺省時,與切片本身等效;
- 兩者同時為 0 時,等效于空切片,一般用于切片復位。
## 直接聲明新的切片
除了可以從原有的數組或者切片中生成切片外,也可以聲明一個新的切片,每一種類型都可以擁有其切片類型,表示多個相同類型元素的連續集合,因此切片類型也可以被聲明,切片類型聲明格式如下:var name []Type,其中 name 表示切片的變量名,Type 表示切片對應的元素類型。
```
package main
import "fmt"
func main() {
// 聲明字符串切片
var strList []string
// 聲明整型切片
var numList []int
// 聲明一個空切片
var numListEmpty = []int{}
// 輸出3個切片
fmt.Println(strList, numList, numListEmpty) // [] [] []
// 輸出3個切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty)) // 0 0 0
// 切片判定空的結果
fmt.Println(strList == nil) // true
fmt.Println(numList == nil) // true
fmt.Println(numListEmpty == nil) // false
}
```
**切片是動態結構,只能與 nil 判定相等,不能互相判定相等。**聲明新的切片后,可以使用 append() 函數向切片中添加元素。
## 使用 make() 函數構造切片
如果需要動態地創建一個切片,可以使用 make() 內建函數,格式如下:
`make( []Type, size, cap )`
其中 Type 是指切片的元素類型,size 指的是為這個類型分配多少個元素,cap 為預分配的元素數量,這個值設定后不影響 size,只是能提前分配空間,降低多次分配空間造成的性能問題。
```
package main
import "fmt"
func main() {
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b) // [0 0] [0 0]
fmt.Println(len(a), len(b)) // 2 2
}
```
其中 a 和 b 均是預分配 2 個元素的切片,只是 b 的內部存儲空間已經分配了 10 個,但實際使用了 2 個元素。
容量不會影響當前的元素個數,因此 a 和 b 取 len 都是 2。
使用 make() 函數生成的切片一定發生了內存分配操作,但給定開始與結束位置(包括切片復位)的切片只是將新的切片結構指向已經分配好的內存區域,設定開始與結束位置,不會發生內存分配操作。
## append()
Go語言的內建函數 append() 可以為切片動態添加元素
```
package main
import "fmt"
func main() {
var a []int
a = append(a, 1) // 追加1個元素
a = append(a, 1, 2, 3) // 追加多個元素, 手寫解包方式
a = append(a, []int{1,2,3}...) // 追加一個切片, 切片需要解包
fmt.Println(a) // [1 1 2 3 1 2 3]
}
```
除了在切片的尾部追加,我們還可以在切片的開頭添加元素
```
package main
import "fmt"
func main() {
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在開頭添加1個元素
a = append([]int{-3,-2,-1}, a...) // 在開頭添加1個切片
fmt.Println(a) // [-3 -2 -1 0 1 2 3]
}
```
在切片開頭添加元素一般都會導致內存的重新分配,而且會導致已有元素全部被復制 1 次,因此,從切片的開頭添加元素的性能要比從尾部追加元素的性能差很多。
## 從切片中刪除元素
Go語言并沒有對刪除切片元素提供專用的語法或者接口,需要使用切片本身的特性來刪除元素,根據要刪除元素的位置有三種情況,分別是從開頭位置刪除、從中間位置刪除和從尾部刪除,其中刪除切片尾部的元素速度最快。
**刪除頭部元素**
```
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
slice = slice[1:] // 刪除開頭1個元素
fmt.Println(slice) // [2 3]
}
```
也可以不移動數據指針,但是將后面的數據向開頭移動,可以用 append 原地完成(所謂原地完成是指在原有的切片數據對應的內存區間內完成,不會導致內存空間結構的變化)
```
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
slice = append(slice[:0], slice[1:]...) // 刪除開頭1個元素
fmt.Println(slice) // [2 3]
}
```
**刪除尾部元素**
```
package main
import "fmt"
func main() {
// 刪除尾部一個元素
slice := []int{1, 2, 3, 4, 5, 6}
// slice = slice[:len(slice)-1]
slice = append(slice[:len(slice)-1], slice[:0]...)
fmt.Println(slice)
}
```
**刪除任意位置元素**
```
package main
import "fmt"
func main() {
slice := []int{1, 2, 3, 4, 5, 6}
index := 3
slice = append(slice[:index], slice[index+1:]...)
fmt.Println(slice)
}
```
## 切片隨機取出元素
```
package main
import (
"fmt"
"time"
"math/rand"
)
func main(){
unPatientIdList := []int{1, 2, 3, 4}
rand.Seed(time.Now().Unix())
randomNum := rand.Intn(len(unPatientIdList)) // 取出的范圍0-3
fmt.Println(unPatientIdList[randomNum])
}
```
```
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
b := a[1:4]
c := b[2:4]
fmt.Println(c) // [4 5]
}
```
**Reslice**
- Reslice時索引以被slice的切片為準
- 索引不可以超過被slice的切片的容量cap()值
- 索引越界不會導致底層數組的重新分配而是引發錯誤
**Append**
- 可以在slice 尾部追加元素
- 可以將一個slice追加在另一個slice尾部
- 如果最終長度未超過追加到slice的容量則返回原始slice
- 如果超過追加到的slice的容量則將重新分配數組并拷貝原始數據