Go语言中切片是对数组的抽象,切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片的创建与数组有所不同,切片是引用类型,它的创建需要用到内置函数 make
,函数make
接受一个类型、一个长度和一个可选的容量参数。调用 make
时,内部会分配一个数组,然后返回数组对应的切片。当容量参数被忽略时,编译器默认容量等于长度。
func main() {
var a []int
a = make([]int,5,5)
fmt.Println(a) // [0 0 0 0 0]
s := make([]int,5)
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 5
}
也可基于现有数组或者数组指针创造切片,以开始和结束索引位置确定所引用的数组片段,不支持反向索引。
使用数组初始化创建切片后,切片会与切片共享底层数组,当修改切片或者数组的值时会相互影响,如果对切片添加数据超出cap限制,则会为新切片对象重新分配数组。
func main() {
x := [...]int{0,1,2,3,4,5,6,7,8,9}
fmt.Println(x[:],len(x[:]),cap(x[:])) // [0 1 2 3 4 5 6 7 8 9] 10 10
fmt.Println(x[2:5],len(x[2:5]),cap(x[2:5])) // [2 3 4] 3 8
fmt.Println(x[2:5:7],len(x[2:5:7]),cap(x[2:5:7])) // [2 3 4] 3 5
fmt.Println(x[4:],len(x[4:]),cap(x[4:])) // [4 5 6 7 8 9] 6 6
fmt.Println(x[:4],len(x[:4]),cap(x[:4])) // [0 1 2 3] 4 10
fmt.Println(x[:4:6],len(x[:4:6]),cap(x[:4:6])) // [0 1 2 3] 4 6
}
从上面这段代码可以看出,如果没有指定切片的容量的话,从数组某个位置开始切片,那么从这个位置一直到最后一个位置的长度就是切片的容量。
切片的默认值是nil
,注意下面两种定义方式的区别,前者仅定义一个[ ]int
类型的变量,并未执行初始化操作,而后者则用初始化表达式完成了全部的创建过程。
func main() {
var a []int
b := []int{}
fmt.Println(a == nil,b == nil) // true false
}
注意,a == nil
表示这是一个未初始化的切片对象,切片本身依然会分配所需内存。
切片不支持比较操作,就算元素类型支持也不行,仅能判断是否为nil
。
func main() {
a := make([]int,1)
b := make([]int,1)
fmt.Println(a == b) // slice can only be compared to nil
}
可以根据索引直接访问,也可以获取元素地址之后根据指针访问,但不能像数组那样直接用指针访问元素内容。
func main() {
s := []int{1, 2, 3, 4, 5, 6}
p := &s
p0 := &s[0]
p1 := &s[1]
fmt.Println(p, p0, p1) // 0xc00000c360 0xc00000c368
// p[0] += 100 // type *[]int does not support indexing 不支持像数组那样的通过指针索引直接访问的方式
(*p)[0] += 100
*p1 += 100
fmt.Println(s) // [101 102 3 4 5 6]
}
可以将切片视为数据源,据此创建新的切片对象,新的切片对象不能超出cap
,但不受len
的限制。新切片对象依旧指向底层数组,也就是说修改对所有的关联切片均可见。
func main() {
s := []int{0,1,2,3,4,5,6,7,8,9}
s1 := s[3:7]
s2 := s1[1:3]
for i:=range s2{
s2[i]+=100
}
fmt.Println(s)
fmt.Println(s1)
fmt.Println(s2)
/*
[0 1 2 3 104 105 6 7 8 9]
[3 104 105 6]
[104 105]
*/
}
根据这个特性,我们可以实现一个栈式的数据结构。
func main() {
stack := make([]int,0,5)
push := func (x int) error{
n := len(stack)
if n==cap(stack){
return errors.New("stack is full")
}
stack = stack[:n+1]
stack[n] = x
return nil
}
pop := func()(int,error){
n := len(stack)
if n==0{
return 0, errors.New("stack is empty")
}
x := stack[n-1]
stack = stack[:n-1]
return x,nil
}
for i:=0;i<7;i++{
fmt.Printf("push %d:%v,%v\n",i,push(i),stack)
}
for i:=0;i<7;i++{
x,err := pop()
fmt.Printf("pop: %d, %v, %v\n",x,err,stack)
}
}
/*
push 0:,[0]
push 1:,[0 1]
push 2:,[0 1 2]
push 3:,[0 1 2 3]
push 4:,[0 1 2 3 4]
push 5:stack is full,[0 1 2 3 4]
push 6:stack is full,[0 1 2 3 4]
pop: 4, , [0 1 2 3]
pop: 3, , [0 1 2]
pop: 2, , [0 1]
pop: 1, , [0]
pop: 0, , []
pop: 0, stack is empty, []
pop: 0, stack is empty, []
*/
切片可以通过使用append
函数来向切片尾部添加数据,返回新的切片对象。
func main(){
a := []int{0,1,2,3,4}
s1 := append(a,5)
s2 := append(a,6,7,8) // 追加多个数据
fmt.Println(a,len(a),cap(a)) // [0 1 2 3 4] 5 5
fmt.Println(s1,len(s1),cap(s1)) // [0 1 2 3 4 5] 6 10
fmt.Println(s2,len(s2),cap(s2)) // [0 1 2 3 4 6 7 8] 8 10
}
数据被追加到原来的底层数组。如果超出cap
限制,则为新切片对象重新分配数组。
注意:
cap
限制,而非底层数组长度限制,因为cap
可小于数组长度。cap
的2倍,而非原数组的2倍。func main(){
s := make([]int,0,100)
s1 := s[:2:4]
s2 := append(s1,1,2,3,4,5,6) // 超出s1的cap的限制,会重新分配底层数组
fmt.Printf("s1:%p : %v\n",&s1[0],s1) // s1:0xc000110000 : [0 0]
fmt.Printf("s2:%p : %v\n",&s2[0],s2) // s2:0xc000010200 : [0 0 1 2 3 4 5 6] 数据地址不同,已重新分配地址
fmt.Printf("s data:%v\n",s[:10]) // s data:[0 0 0 0 0 0 0 0 0 0] append并未向原数组写入数据
fmt.Printf("s1 cap: %d,s2 cap:%d\n",cap(s1),cap(s2)) // s1 cap: 4,s2 cap:8 新的数组长度是原数组长度的2倍
}
向nil
切片追加数据时,会为其分配底层数组。
func main(){
var a []int
a = append(a,1,2,3,4,5,6)
fmt.Println(a) // [1 2 3 4 5 6]
}
正是由于存在重新分配底层数组的缘故,建议在定义时预留足够多的空间,避免中途内存分配和数据复制开销。
copy
函数的使用格式如下:copy( destSlice, srcSlice []T) int
,其中 srcSlice
为数据来源切片,destSlice
为复制的目标(也就是将 srcSlice
复制到 destSlice
),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy()
函数的返回值表示实际发生复制的元素个数。
func main(){
s := []int{0,1,2,3,4,5,6,7,8,9}
s1 := s[5:8]
n := copy(s[4:],s1) // 在同一底层数组的不同区间复制 copy([4,5,6,7,8,9],[5,6,7]) ---> [5,6,7,7,8,9]
fmt.Println(n,s) // 3 [0 1 2 3 5 6 7 7 8 9]
s2 := make([]int,6)
n = copy(s2,s)
fmt.Println(n,s2) // 6 [0 1 2 3 5 6]
}
还可以从字符串中复制数据到[ ]byte
。
func main(){
b := make([]byte,3)
n := copy(b,"abcd")
fmt.Println(n,b) // 3 [97 98 99]
}