slice (切片)
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型。它是基于数组类型做的一层封装。
一个slice由三个部分构成:指针、长度和容量。
- 指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。
- 长度对应slice中元素的数目;长度不能超过容量
- 容量一般是从slice的开始位置到底层数据的结尾位置。
- 内置的len和cap函数分别返回slice的长度和容量。
结构
// %GOROOT%src
untimesilce.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
指向底层数组的指针、切片的元素数量和底层数组的容量
切片声明
// 切片声明
var varName []T
// varName:表示变量名
// T:表示切片中的元素类型
// 切片与数组声明对比,数组指定元素数量
// 数组声明语法
var 数组变量名 [元素数量]Type
切片初始化
slice的字面值也可以按顺序指定初始化值序列,或者是通过索引和元素值指定,或者的两种风格的混合语法初始化。
//声明一个整型切片并初始化
var a = []int{}
s1 := []int{0, 1, 2, 3, 4, 5}
s2 := []int{0:1, 1:2, 2:4}
s3 := []int{0:1, 1:2, 3}
package main
import "fmt"
// 反转
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
func main() {
// 初始化数组a
a := [...]int{0, 1, 2, 3, 4, 5}
s1 := a[:]
reverse(a[:])
fmt.Println(a) // 输出结果 [5 4 3 2 1 0]
fmt.Println(s1) // [5 4 3 2 1 0]
reverse(s1[:2])
fmt.Println(s1) // [4 5 3 2 1 0]
fmt.Println(a) // [4 5 3 2 1 0] 底层数组元素也改变
// 初始化切片s
s := []int{0, 1, 2, 3, 4, 5}
reverse(s[:2]) // [1 0 2 3 4 5]
reverse(s[2:]) // [1 0 5 4 3 2]
reverse(s)
fmt.Println(s) // [2 3 4 5 0 1]
}
注意:slice类型的变量s和数组类型的变量a的初始化语法的差异。slice和数组的字面值语法很类似,它们都是用花括弧包含一系列的初始化元素,但是对于slice并没有指明序列的长度。
切片的创建
由数组创建
array[b:e],其中,array表示数组名;b表示开始索引,可以不指定,默认是0;e表示结束索引,可以不指定,默认是len(array)。array[b:e]表示创建一个包含e-b个元素的切片,第一个元素是array[b],最后一个元素是array[e-1]
package main
import "fmt"
func main() {
var array = [...]int{0,1,2,3,4,5,6} // 创建有7个 int 型元素的数组
s1 := array[0:4]
fmt.Printf("%v
", s1) // [0 1 2 3]
}
内置函数make创建
内置的make函数动态创建一个指定元素类型、长度和容量的slice。切片各元素被默认初始化为切片元素类型的零值。
格式
make([]T, len)
make([]T, len, cap)
例子
package main
import "fmt"
func main() {
a := make([]int, 3)
fmt.Println(a)
fmt.Printf("len(a):%v cap(a):%v
", len(a), cap(a))
b := make([]int , 10, 15)
fmt.Printf("len(b):%v cap(b):%v
", len(b), cap(b))
}
切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
,切片s1 := a[:5]
,相应示意图如下。
切片s2 := a[3:6]
,s2下标上限cap(a)=len(a)=8相应示意图如下:
切片的操作
slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。
package main
import "fmt"
func main() {
// 初始化数组
months := [...]string{1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"}
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2) // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]
for _, s := range summer {
for _, q := range Q2 {
if s == q {
fmt.Printf("%s appears in both
", s)
}
}
}
}
切片的长度和容量
切片拥有自己的长度和容量,可以使用内置的len()
函数求长度,使用内置的cap()
函数求切片的容量。
package main
import "fmt"
func main() {
// 初始化切片
var a = []int{1,2}
fmt.Printf("len(s):%v cap(s):%v
", len(a), cap(a))
}
判断切片是否为空
检查切片是否为空,要使用len(s) == 0
来判断,而不应该使用s == nil
来判断。
package main
import "fmt"
func main() {
// 初始化切片
var a = []int{1,2}
if len(a) == 0 {
fmt.Println("切片为空")
} else {
fmt.Println("切片元素非空")
}
}
切片元素比较
切片之间不能比较,因此我们不能使用==
操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
for i := range x {
if x[i] != y[i] {
return false
}
}
return true
}
赋值拷贝
// 拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
func main() {
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
}
遍历
package main
import "fmt"
func main() {
s := []int{1, 3, 5}
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
for index, value := range s {
fmt.Println(index, value)
}
}
切片添加元素
内建函数append()
可以为切片动态添加元素。 可以一次添加一个元素或多个元素,也可以添加另一个切片中的元素(后面加…)。
package main
import "fmt"
func main(){
var s []int // []
s = append(s, 1) // [1]
// 追加多个元素
s = append(s, 2, 3, 4) // [1 2 3 4]
s2 := []int{5, 6, 7}
// 追加另一个切片s2到s
s = append(s, s2...) // [1 2 3 4 5 6 7]
}
注意:通过var声明的零值切片可以在append()
函数直接使用,无需初始化。
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”(每次扩容后都是扩容前的2倍的容量),此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()
函数调用时,所以我们通常都需要用原变量接收append函数的返回值。
package main
import "fmt"
func main() {
//append()添加元素和切片扩容
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p
", numSlice, len(numSlice), cap(numSlice), numSlice)
}
}
删除切片的元素
func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除切片的索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]
}
切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
切片修改元素值
func main() {
a := []int{1, 2, 3, 4, 5}
b := a
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 1000
fmt.Println(a) //[1000 2 3 4 5]
fmt.Println(b) //[1000 2 3 4 5]
}
// 由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
切片的复制
内建的copy()
函数可以迅速地将一个切片的数据复制到另外一个切片空间中
copy(destSlice, srcSlice []T)
// srcSlice: 数据来源切片
// destSlice: 目标切片
示例:
func main() {
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
}
切片的扩容策略
通过查看$GOROOT/src/runtime/slice.go
源码
成倍自动扩容切片容量数量