定义:
数组是值类型,是一组同类型数据的集合,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度。
切片的长度是不固定的,可以追加元素,数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型Slices切片(“动态数组"),在追加时可能使切片的容量增大。切片中有两个概念:一是len长度,二是cap容量,长度是指已经被赋过值的最大下标+1,可通过内置函数len()获得。容量是指切片目前可容纳的最多元素个数,可通过内置函数cap()获得。切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
数据与切片的区别:
一、数据是固定长度,切片是可变长度
二、数据是值传递(这点是和其它语言不一样的地方),切片是地址传递。这也是经常在go语言代码中看到的传递数组时要加上&,而在接收函数中要加上*,以这种方式把数据变为地址传递,用切片也可以达到同样的效果。
切片的存在形态:
切片本身是无值的,他是对数组底层数据的镜像,所以把切片传入到被调用函数中后,被调用函数中的数据改了,数据的值也会跟着变。
另外还可以对切片进行reslice,但是不管reslice多少次,这些slice都是对底层数据的镜像,本身都是无值的。
切片取值超出了数组的边界怎么办?介绍一下slice的扩展
先举个例子:
arr := […]int{0,1,2,3,4,5,6,7}
// arr[2:6]的取值是{2,3,4,5} , 几乎数据语言的取值下标都是半开半闭区间的,即本例中包函下标2但不包函下标6的元素
slice1 := arr[2:6]
//slice2等于是从{2,3,4,5}中,从下标3取到下标5,但这里有个问题,slice1中并没有下标为4的元素,这里我们能取到吗? 答案是可以的,切片可以向后扩展取值,但是不可以向前扩展
slice2 := slice1[3,5]
//可以向后扩展不可以向前扩展的意思在哪呢?先看下图:
也就是说,slice其实是有三个参数值来定义的,ptr是指slice在数据中的超始位置,len是指slice向后取多少位,cap是指从超始位置到数据最后一个数据的长度。从这张图可以看出slice1[4]是存在于灰色部分,是可以向后扩展到的,所以仍然可以取到值。下图具体说明上面代码取值的内存结构
可以这样打印出数据的相应参数:
fmt.printf(“slice1=%v , len(slice1) = %d, cap(slice1)= %d”,s1,len(slice1),cap(slice1))
向slice中添加元素
arr2 = append(slice1,10)
fmt.printf(arr1,slice1)
这里会发现向slice1中添加元素后,slice1底层的数组并未改变,但是slice1的值却变了,按照我们上面说的,arr1是slice1底层数据,那么显然在append后,slice1底层的数据就变了,变成了arr2.
总结:
-
向slice中添加元素时,若元素个数超越了底层数据的长度,那么系统会重新为slice分配底层数组
arr1 := [...]int{0,1,2,3,4,5} slice1 := arr1[2:6] arr2 := append(slice1,6) fmt.Println(arr1,slice1,arr2)
输出结果显示arr1中的值未改变,且分配了新的数组arr2:
[0 1 2 3 4 5] [2 3 4 5] [2 3 4 5 6]
-
向slice中添加元素时,若元素个数未超越了底层数据的长度,那么系统会覆盖底层数据对应下标位置的数据,如:
arr1 := [...]int{0,1,2,3,4,5} slice1 := arr1[2:5] arr2 := append(slice1,6) fmt.Println(arr1,slice1,arr2)
输出结果显示arr1中的值变了,且也分配了新的数组arr2:
[0 1 2 3 4 6] [2 3 4] [2 3 4 6]
非基于数组来定义slice
上面都是基于数据来定义的slice,下面看一下如何直接定义slice以及slice的另一些特性,先看一段代码:
package main
import "fmt"
func main() {
var s []int
printfSlice(s)
for i :=0;i<5;i++{
s = append(s,i)
printfSlice(s)
}
fmt.Println(s)
}
func printfSlice(s []int) {
fmt.Println("s=%v , len(s)=%d , cap(s)=%d",s,len(s),cap(s))
}
输了结果:
s=%v , len(s)=%d , cap(s)=%d [] 0 0
s=%v , len(s)=%d , cap(s)=%d [0] 1 1
s=%v , len(s)=%d , cap(s)=%d [0 1] 2 2
s=%v , len(s)=%d , cap(s)=%d [0 1 2] 3 4
s=%v , len(s)=%d , cap(s)=%d [0 1 2 3] 4 4
s=%v , len(s)=%d , cap(s)=%d [0 1 2 3 4] 5 8
[0 1 2 3 4]
从输出结果可以看到以下几个结论:
- slice定义时就已经被分配置了空间,并且被赋与初始值0. go中没有null用nil来表示,但是nil是有空间的可操作的只不过是初始值
- cap的增长是自动的并且是默认2倍的增长空间,并且按上文所述超过了cap长度时会分配新的底层数组
make创建slice
先上代码
package main
import "fmt"
func main() {
s1 := make([]int,3)
s2 := make([]int,3,10)
printfSlice(s1)
printfSlice(s2)
}
func printfSlice(s []int) {
fmt.Println("s=%v , len(s)=%d , cap(s)=%d",s,len(s),cap(s))
}
输入结果:
s=%v , len(s)=%d , cap(s)=%d [0 0 0] 3 3
s=%v , len(s)=%d , cap(s)=%d [0 0 0] 3 10
结果分析:
- make可以指定创建的slice len,且cap默认等于len
- make可以指定cap的容量大小,如果你预期你的数据会比较大,可以把初始容量设置大一些,可以减少一些内存分配的过程
slice的常用操作
上代码
package main
import "fmt"
func main() {
s := []int{0,1,2,3}
fmt.Println("copy slice")
s1 := make([]int,8)
copy(s1,s)
printfSlice(s1)
fmt.Println("delete slice middle element")
s2 := append(s1[:3],s1[4:]...)
fmt.Println(s2)
fmt.Println("delete slice left element")
s3 := []int{0,1,2,3,4,5,6,7,8}
s4 := s3[1:]
printfSlice(s4)
fmt.Println("delete slice right element")
s5 := []int{0,1,2,3,4,5,6,7,8}
s6 := s5[:len(s5)-1]
printfSlice(s6)
}
func printfSlice(s []int) {
fmt.Println("s=%v , len(s)=%d , cap(s)=%d",s,len(s),cap(s))
}
输了结果:
copy slice
s=%v , len(s)=%d , cap(s)=%d [0 1 2 3 0 0 0 0] 8 8
delete slice middle element
[0 1 2 0 0 0 0]
delete slice left element
s=%v , len(s)=%d , cap(s)=%d [1 2 3 4 5 6 7 8] 8 8
delete slice right element
s=%v , len(s)=%d , cap(s)=%d [0 1 2 3 4 5 6 7] 8 9
结果中显示 ,delete left element时cap未变,但是delete right element时cap变了,目前我没想明白为什么会变,但是它确实有这个特性