slice代表变长的序列,它的底层是数组。一个切片由3部分组成:指针、长度和容量。指针指向底层数组,长度代表slice当前的长度,容量代表底层数组的长度。
换句话说,slice自身维护了一个指针属性,指向它底层数组的某些元素的集合。
type slice struct {
array unsafe.Pointer // 指向底层数组
len int // 长度
cap int // 容量
}
一般有如下方法创建切片。
slice := make([]T, 5)
上述代码创建了一个整型切片,其长度为5。若不传入容量的大小,则容量和长度相同。
slice := make([]T, 3, 5)
上述代码创建了一个整型切片,其长度为3,容量为5。
slice := []int{1, 2, 3, 4}
上述代码创建了长度和容量均为4的整型切片。
slice[i:j:k]
i代表起始位置,切片的长度为(j-i),切片的容量为(k-i)。如果没有指定k,则表示切到底层数组的尾部。此外还有几种简化形式:
slice[i:] // 从i切到尾部
slice[:j] // 从头部切到j,不包括j
slice[:] // 从头切到尾
声明一个slice,但是不初始化,这个slice就是nil slice。nil slice表示它的指针为nil,也就是这个slice不会指向底层数组,因此它的长度和容量都为0。
var slice []int
创建一个长度为0的slice,就是空slice。空slice的长度和容量也为0,但是它会指向一个底层数组,只不过底层数组是长度为0的空数组。
slice := make([]int,0)
可以将一个slice拷贝到另一个slice中。copy()函数表示将src拷贝到dst。若src比dst长,则截断;若src比dst短,则只拷贝src的部分。返回值是拷贝成功的元素数量。
func copy(dst, src []Type) int
示例:
s1 := []int{11, 22, 33}
s2 := make([]int, 5)
s3 := make([]int,2)
num := copy(s2, s1)
copy(s3,s1)
// [11 22 33] 0xc0000160a8
fmt.Printf("the value of s1 is %v, the value of ptr of s1 is %p\n", s1, s1)
// [11,22,33,0,0] 0xc00001c120
fmt.Printf("the value of s2 is %v, the value of ptr of s2 is %p\n", s2, s2)
// [11,22] 0xc00001a2d0
fmt.Printf("the value of s3 is %v, the value of ptr os s3 is %p\n", s3, s3)
s1拷贝到s2时,因s1的长度小于s2的长度,只拷贝s1的部分,则s2为[11,22,33,0,0];s1拷贝到s3时,因s3的长度小于s1的长度,所以截断s1,只拷贝前两个元素,则s3为[11, 22]。
copy()操作只是拷贝内容,各切片的底层数组仍然是独立的。
使用append()函数可以追加元素。
在append时,如果切片的容量已经不能容纳将要追加的数据,就会创建一个新的扩容后的底层数组,将之前的数据拷贝过去后,再执行扩容操作;如果切片的容量足以容纳,那么就会在原数组执行扩容操作。
目前扩展底层数组的逻辑为:按照当前底层数组长度的2倍进行扩容;如果底层数组的长度超过1000,将按照125%扩容。
下述代码分别测试了在不会扩容和会扩容的前提下,执行append操作的结果。
func main() {
var slice = []int{1, 2, 3, 4, 5} // len = 5; capacity = 5
var newSlice = slice[1:3] // len = 2; capacity = 4(已经使用了两个位置,还有两个位置可以append)
fmt.Printf("%p\n", slice) // 0xc00001c120
fmt.Printf("%p\n", newSlice) // 0xc00001c128; newSlice的地址指向的是slice[1]的地址,因此底层使用的是同一个数组
fmt.Printf("%v\n", slice) // [1 2 3 4 5]
fmt.Printf("%v\n", newSlice) // [2 3]
newSlice[1] = 6 // 更改后slice、newSlice都改变了
fmt.Printf("%v\n", slice) // [1 2 6 4 5]
fmt.Printf("%v\n", newSlice) // [2 6]
newSlice = append(newSlice, 7, 8) // append操作之后,array的len和capacity不变, newArray的len变为4,capacity仍然为4
fmt.Printf("%v\n", slice) //[1 2 6 7 8]; newSlice改变了底层数组的内容,所以slice的内容也变了
fmt.Printf("%v\n", newSlice) //[2 6 7 8]
newSlice = append(newSlice, 9, 10) // newSlice的len已经等于cap,再次append会创建一个新的底层数组(已扩容),并将array指向的底层数组拷贝过去,并追加新值。
fmt.Printf("%p\n", slice) // 0xc00001c120; slice指向的底层数组未改变
fmt.Printf("%p\n", newSlice) // 0xc00009e000; newSlice指向的底层数组有改变
fmt.Printf("%v\n", slice) // [1 2 6 7 8]
fmt.Printf("%v\n", newSlice) // [2 6 7 8 9 10]
}
在Go语言中,函数的参数都是按值传递的,因此在调用函数时,会将参数的副本传递给函数。在传递slice时,虽然传递的是副本,但是副本同样指向了源slice的底层数组,所以在函数内部修改slice,有可能会影响到底层数组,进而影响到其他slice。
func main() {
slice := []int{1, 2}
// [1 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
change(slice)
// [3 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
return
}
func change(slice []int) {
slice[0] = 3
// [3 2] 0xc00000e090 0xc00001a2d0
fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
}
func main() {
slice := []int{1, 2}
// [1 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
change(slice)
// [1 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
slice[0] = 4
// [4 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
return
}
func change(slice []int) {
slice = append(slice, 3)
// [1 2 3] 0xc00000e090 0xc0000180e0
fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
}
上述代码在change()内部为slice追加元素3,由于slice的长度和容量均为2,append()操作会导致slice的副本指向一个新的底层数组,因此slice的副本和slice指向的底层数组不再为同一个。
在调用change()后,将slice下标为0的值修改为4,输出slice的值为[4,2]而非[1,2,3],再次说明了change()函数内部的slice和main()函数的slice已经不再指向同一个底层数组。
如果希望获取到函数调用后的slice(),有如下两种方法:
func main() {
slice := []int{1, 2}
// [1 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
slice = change(slice)
// [1 2 3] 0xc00000e048 0xc0000180e0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
slice[0] = 4
// [4 2 3] 0xc00000e048 0xc0000180e0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
return
}
func change(slice []int) []int {
slice = append(slice, 3)
// [1 2 3] 0xc00000e090 0xc0000180e0
fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
return slice
}
func main() {
slice := []int{1, 2}
// [1 2] 0xc00000e048 0xc00001a2d0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
change(&slice)
// [1 2 3] 0xc00000e048 0xc0000180e0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
slice[0] = 4
// [4 2 3] 0xc00000e048 0xc0000180e0
fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
return
}
func change(slice *[]int) {
*slice = append(*slice, 3)
// [1 2 3] 0xc00000e048 0xc0000180e0
fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", *slice, slice, *slice)
}
func main() {
slice := make([]int, 2, 5)
slice1 := slice
slice[1] = 3
fmt.Printf("slice is %v, slice1 is %v\n", slice, slice1) // [0 3] [0 3]
fmt.Printf("addr of slice is %p, addr of slice1 is %p\n", &slice, &slice1) // 0xc00000e048 0xc00000e060
fmt.Printf("value of ptr of slice is %p, value of ptr of slice1 is %p\n", slice, slice1) // 0xc00001c120 0xc00001c120
}