Go 語言切片是對數組的抽象。Go 數組的長度不可改變,在特定場景中這樣的集合就不太適用,Go 中提供了一種靈活、功能強悍的內置類型切片 ("動態數組"),與數組相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。
?
切片是一個在 Go 語言中引入的新理念,它有一些特征如下:
?
- 對數組抽象
- 數組長度不固定
- 可追加元素
- 切片容量可增大
1、定義切片
你可以聲明一個未指定大小的數組來定義切片:
?
var identifier []type
?
切片不需要說明長度。
?
示例:
?
var s []int // 定義一個整形大小不定的切片,變量名稱 s
?
除此之外,切片還有其他幾種定義方式:
?
var (a []int // nil 切片,和 nil 相等,一般用來表示一個不存在的切片b = []int{} // 空切片,和 nil 不相等,一般用來表示一個空的集合c = []int{1, 2, 3} // 有3個元素的切片,len=3,cap=3d = c[:2] // 有2個元素的切片,len=2,cap=3e = c[0:2:cap(c)] // 有2個元素的切片,len=2,cap=3 f = c[:0] // 有0個元素的切片,len=0,cap=3g = make([]int, 3) // 有3個元素的切片,len=3,cap=3 h = make([]int, 2, 3) // 有2個元素的切片,len=2,cap=3 i = make([]int, 0, 3) // 有0個元素的切片,len=0,cap=3
)
?
本質:
切片本身是一個三個字段的數據結構:
?
type SliceHeader struct {Data uintptr // 指向底層數組的指針Len int // 切片中元素的個數,通過 len(s) 獲取Cap int // 切片的容量(不需重新分配內存前,可容納的元素數量),通過 cap(s) 獲取
}
?
區分數組的聲明和切片的聲明方式:
當使用字面量來聲明切片時,其語法與使用字面量聲明數組非常相似。二者的區別是:如果在?[]
?運算符里指定了一個值,那么創建的就是數組而不是切片。只有在?[]
?中不指定值的時候,創建的才是切片。
2、切片初始化
s := []int{1, 2, 3} // 直接初始化切片,[] 表示是切片類型,{1, 2, 3} 初始化值依次是1, 2, 3,其 cap=len=3
s := arr[:] // 初始化切片 s,是數組 arr 的引用
s := arr[startIndex:endIndex] // 將 arr 中從下標 startIndex 到 endIndex-1 下的元素創建為一個新的切片
s := arr[startIndex:] // 缺省 endIndex 時將表示一直到 arr 的最后一個元素
s := arr[:endIndex] // 缺省 startIndex 時將表示從 arr 的第一個元素開始
s1 := s[startIndex:endIndex] // 通過切片 s 初始化切片 s1
s := make([]int, len, cap) // 通過內置函數 make() 初始化切片 s,[]int 標識為其元素類型為 int 的切片
3、訪問
切片只能訪問其長度范圍內的內容,通過下標訪問:
?
s[i] = 10 // 寫操作
v := s[i] // 讀操作
?
迭代方式訪問:
切片是一個集合,可以通過?range
?迭代其中的元素:
?
- for 循環方式的迭代:
?
var slice = []string{"Red", "Yellow", "Blue", "Green", "Gray"}
// for 循環迭代
for i := 0; i < len(slice); i++ {fmt.Println(i, slice[i])
}
?
- range 遍歷:
?
for index, value := range slice {fmt.Printf("index: %d, value: %s\n", index, value)
}
?
注意:
?
range
?返回的第二個值是對應元素的一份副本,不能用于修改;若要修改則需要通過索引。- 迭代方式遍歷時,不能對切片進行操作(添加、或刪除元素),否則會引發異常。
5、len () 和 cap () 函數
切片是可索引的,并且可以由?len()
?方法獲取長度。切片提供了計算容量的方法?cap()
?可以測量切片最長可以達到多少。
?
示例:
?
package main
import "fmt"func main() {var numbers = make([]int, 3, 5)printSlice(numbers)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
?
輸出結果:
?
len=3 cap=5 slice=[0 0 0]
6、空 (nil) 切片
一個切片在未初始化之前默認為?nil
,長度為 0。
?
示例:
?
package main
import "fmt"func main() {var numbers []intprintSlice(numbers)if numbers == nil {fmt.Printf("切片是空的")}
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
?
輸出結果:
len=0 cap=0 slice=[]
切片是空的
7、切片的增刪改查操作
1. 切片尾部新增元素
var slice []int
// 新增一個元素
slice = append(slice, 1)
// 新增多個元素
slice = append(slice, 1, 2)
// 新增多個元素,切片作為參數,需要使用 ... 運算符來輔助解構切片
var newSlice = []int{1, 2, 3}
slice = append(slice, newSlice...) // ... 不能省略
2. 切片首部新增元素
// 切片首部增加元素
var slice = []int{1, 2}
// 首部增一個元素
slice = append([]int{5}, slice...)
// 首部增多個元素
var newSlice = []int{5, 6, 7}
slice = append(newSlice, slice...)
3. 切片中間新增元素
// 切片中間某個位置插入元素
var slice = []int{1, 2, 3}
// 比如需要插入到元素索引 i 后,則先以 i+1 為切割點,把 slice 切割成兩半
// 索引 i 前數據:slice[:i+1],索引 i 后的數據:slice[i+1:]
// 然后再把索引 i 后的數據:slice[i:] 合并到需要插入的元素切片中
// 最后再把合并后的切片合并到索引 i 前數據:slice[:i]
// 如在元素索引 1 后增加元素
slice = append(slice[:2], append([]int{6, 7}, slice[2:]...)...)
刪除操作
var slice = []int{1, 2, 3, 4, 5, 6}
// 從切片首部刪除
slice = slice[1:]
// 從切片尾部刪除2個
slice = slice[:len(slice) - 2]
// 從切片中間刪除,如從索引為 i,刪除2個元素(i+2)
slice = append(slice[:1], slice[3:]...)
其他操作
// 修改元素
var slice = []int{1, 2, 3}
slice[1] = 6// 查找元素
var slice = []int{1, 2, 3}
log.Println("slice[1]=", slice[1])// 試圖訪問超出其長度的元素就會報錯
a := slice[4] // runtime error: index out of range [4] with length 3
log.Println(a)
課堂練習
使用切片的增刪改查功能完成一個簡單用戶信息錄入和維護程序。