Go 语言切片是对数组的抽象。
Go语言中数组是具有固定长度而且拥有零个或者多个相同或相同数据类型元素的序列。由于数组长度固定,所以在Go语言比较少直接使用。而slice长度可增可减,使用场合比较多。更深入的区别在于:数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址。为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个Slice,Slice也可以继续衍生Slice。
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
fmt.Println("arr[2:6] =",arr[2:6])
fmt.Println("arr[:6] =",arr[:6])
fmt.Println("arr[2:] =",arr[2:])
fmt.Println("arr[:] =",arr[:])
}
运行结果如下:
使用切片作为参数
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
fmt.Println("arr[2:6] =",arr[2:6])
fmt.Println("arr[:6] =",arr[:6])
s1 := arr[2:]
fmt.Println("s1 =",s1)
s2 := arr[:]
fmt.Println("s2 =",s2)
fmt.Println("After updateSlice...")
updateSlice(s1)
fmt.Println("s1 = ",s1)
fmt.Println("arr = ",arr)
}
运行结果如下:
可以看到,使用切片作为参数传递时,当切片里面的内存改变时,源数组的值也改变了。
Slice再次Slice
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
s2 := arr[:]
fmt.Println("s2 =",s2)
fmt.Println("Reslices ...")
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)
}
运行结果如下:
Slice就相当于在源数组上建立了一个view
Slice的扩展
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println("s1 =",s1)
fmt.Println("s2 = ",s2)
}
上面的s1/s2分别等于多少?s2会不会报错?
运行结果如下:
为什么s2取值是5和6而不是报错?
数组和slice其实是紧密关联的。slice可以看成是一种轻量级的数据结构,可以用来访问数组的部分或者全部元素,而这个数组称之为slice的底层数组。slcie有三个属性:指针,长度和容量。指针指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素。长度指的是slice中的元素个数,不能超过slice的容量。指针通常是从指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素。长度指的是slice中的元素个数,它不能超过slice的容量。容量的大小通常大于等于长度,会随着元素个数增多而动态变化。Go语言的内置函数len 和 cap 用来返回slice的长度和容量。
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Printf("s1 = %v, len(s1)=%d, cap(s1)=%d\n",s1,len(s1),cap(s1))
fmt.Printf("s2 = %v, len(s2)=%d, cap(s2)=%d\n",s2,len(s2),cap(s2))
}
运行结果如下:
所以说slice可以向后扩展。
向Slice添加元素
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Printf("s1 = %v, len(s1)=%d, cap(s1)=%d\n",s1,len(s1),cap(s1))
fmt.Printf("s2 = %v, len(s2)=%d, cap(s2)=%d\n",s2,len(s2),cap(s2))
s3 := append(s2,10)
s4 := append(s3,11)
s5 := append(s4,12)
fmt.Println("s3,s4,s5 = ",s3,s4,s5)
fmt.Println(arr)
}
运行结果如下:
从运行结果我们可以看到s2=[5,6],它的容量是3,s3=[5,6,10],s4=[5,6,10,11],s5=[5,6,10,11,12],append追加操作是可以做到,它不管我们原先的cap是多少,从最后的arr输出可以看到arr的长度还是8,没有改变,s2=[5,6],追加一个10之后,就把7变成了10,再append 11和12,此时s4和s5就不是view的arr了,而是一个新的数组,go语言会在内部创建一个新的array,把arr的内容copy过去,新的array的长度会设置更长一些。原先的数组如果没有新的使用的话,会被垃圾回收掉。
向Slice添加元素
- 添加元素时,如果超过cap,系统会重新分配更大的底层数组
- 由于值传递的关系,必须接受append的返回值
- s = append(s,val)
Slice的创建
方式一:
func main() {
var s []int //Zero value for slice is nil
for i :=0;i<100;i++{
s = append(s,2*i+1)
}
fmt.Println(s)
}
运行结果如下:
可以看到打印了100 个奇数,那么此过程之中的数组长度和容量的变化是怎样的?
len是逐渐递增的,cap每次装不下的时候就扩充乘以2。
方式二:
func main() {
s := []int{2,4,6,8,10}
fmt.Println(s)
}
方式三:
知道slice的长度,但不知道里面的内从,可以使用内置函数make来创建,make里面可以跟len和cap
func main() {
s := []int{2,4,6,8,10}
fmt.Println(s)
s1 := make([]int,16)
s2 := make([]int,10,40)
printSlice(s1)
printSlice(s2)
}
运行结果如下:
Slice的copy
func main() {
s := []int{2,4,6,8,10}
fmt.Println(s)
s1 := make([]int,16)
s2 := make([]int,10,40)
printSlice(s1)
printSlice(s2)
fmt.Println("After Copy ...")
copy(s1,s)
fmt.Println(s1)
}
运行结果如下:
Slice的删除
如把上面s1里面的8删掉,go本身并没有内置的删除函数,但是可以使用切片的append
func main() {
s := []int{2,4,6,8,10}
fmt.Println(s)
s1 := make([]int,16)
s2 := make([]int,10,40)
printSlice(s1)
printSlice(s2)
fmt.Println("After Copy ...")
copy(s1,s)
fmt.Println(s1)
s1 = append(s1[:3],s1[4:]...)
fmt.Println(s1)
}
运行结果如下:
可以看到,删除一个元素之后,长度变小了,但是容量不变。
Slice删除头尾