1)简介
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
2)定义切片
声明一个未指定大小的数组来定义切片:
// 声明一个未指定大小的数组来定义切片
var slice []type
// 使用make函数来创建切片
var slice1 []type = make([]type, len) //len是数组长度,亦是切片的初始长度
// or
slice2 := make([]type, len)
3)初始化切片
// 直接初始化切片s
s := []int {1,2,3}
// 初始化切片s,是数组arr的引用
s := arr[:]
// 以arr中从下标startIndex至endIndex-1间的元素去创建一个新的切片
s := arr[startIndex:endIndex]
// 规则同上,默认至最后一个元素处结束
s := arr[startIndex:]
// 规则同上,默认从第一个元素处开始
s := arr[:endIndex]
// 用切片去初始化切片
s1 := s[startIndex:endIndex]
拓展:len() 与 cap()
len()代表切片的引用长度
cap()代表切片的容量
“容量”的用处:
当用 append扩展长度时,如果新的长度小于容量,不会更换底层数组,否则,go 会新申请一个底层数组,拷贝这边的值过去,把原来的数组丢掉。也就是说,容量的用途是:在数据拷贝和内存申请的消耗与内存占用之间提供一个权衡。
“长度”的用处:
为了在使用时限制切片可用成员的数量,提供边界查询的。所以用 make 申请好空间后,需要注意不要越界(越 len )。
4)空切片
一个切片在未初始化之前默认为 nil,长度为 0。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
if(numbers == nil){
fmt.Printf("切片是空的")
}
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// 运行结果
len=0 cap=0 slice=[]
切片是空的
5)append() 与 copy()
函数原型
// 往切片中添加元素
func append(slice []Type, elems ...Type) []Type
// 从切片中复制元素,返回复制的元素个数,等于源和目的的最小长度值
func copy(dst, src []Type) int
对于slice进行append操作,遵循以下原则:
1. append 函数对一个切片 slice 进行追加操作,并返回另一个长度为 len(slice) + 追加个数 的切片,原切片不被改动,两个切片所指向的底层数组可能是同一个也可能不是,取决于第二条:
2. slice 是对其底层数组的一段引用,若 append 追加完之后没有突破 slice 的容量,则实际上只是追加的数据改变了其底层数组对应的值,并且 append 函数返回对底层数组新的引用(切片);若 append 追加的数据量突破了 slice 的最大容量(底层数组长度固定,无法增加长度赋予新值),则 Go 会在内存中申请新的数组(数组内的值为追加操作之后的值),并返回对新数组的引用(切片)。
3. 扩容规则:
3.1 在1024字节以内时:
3.1.1、当同时添加多个元素时,
3.1.1.1、len[list]+len[params]>=2*cap:
len(list)+len([params]) 为偶数:cap=len(list)+len([params])
len(list)+len([params]) 为奇数:cap=len(list)+len([params])+1
3.1.1.2、cap<len[list]+len[params]<2*cap:
cap=2*cap
即 cap 始终为偶数。3.1.2、当一个一个添加元素时:
len(list)+1<=cap: cap=cap
len(list)+1>cap: cap=2*cap
即 cap 总是呈 2 倍的增加(也是偶数)。3.2 大于1024字节时,扩容cap的1/4(即newcap=cap+cap*1/4)。
4. 特别说明:本文只是简单性的展示扩容结论,欲深究可以移步这里参考。
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers) // 多读两遍上面的“扩容规则”即可理解下边的运行结果
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// 运行结果
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
再来一个栗子进行深入理解:
// 声明并初始化长度为 5 的整型数组 [0 0 0 0 0]
var arr [5]int
// slice1 和 slice2 是对 arr 第 2 个元素到第 4 个元素的引用
slice1 := arr[1:4] // slice1: [0 0 0]
slice2 := arr[1:4] // slice2: [0 0 0]
// 对切片的修改会反映到底层数组
slice1[0] = 1 // slice1:[1 0 0] slice2:[1 0 0] arr:[0 1 0 0 0]
// 对底层数组的修改同样会反映到指向它的切片
arr[2] = 2 // slice1:[1 2 0] slice2:[1 2 0] arr:[0 1 2 0 0]
// 因为对 slice1 的追加没有突破其底层数组的长度,所以返回的切片还是指向原来的底层数组
slice3 := append(slice1, 4) // slice1:[1 2 0] slice2:[1 2 0] slice3:[1 2 0 4] arr:[0 1 2 0 4]
slice3[2] = 3 // slice1:[1 2 3] slice2:[1 2 3] slice3:[1 2 3 4] arr:[0 1 2 3 4]
// 如果对切片的追加突破了底层数组的长度,则会分配一个新的数组,返回指向新数组的切片
slice3 = append(slice3, 5) // slice1:[1 2 3] slice2:[1 2 3] slice3:[1 2 3 4 5] arr:[0 1 2 3 4]
// slice3 的底层数组已经改变,对它的操作不会影响到 slice1 slice2 和 arr
slice3[0] = 6 // slice1:[1 2 3] slice2:[1 2 3] slice3:[6 2 3 4 5] arr:[0 1 2 3 4]
拓展:(1) 合并多个数组
package main
import "fmt"
func main() {
var arr1 = []int{1,2,3}
var arr2 = []int{4,5,6}
var arr3 = []int{7,8,9}
var s1 = append(append(arr1, arr2...), arr3...)
fmt.Printf("s1: %v\n", s1)
}
//运行结果
//s1: [1 2 3 4 5 6 7 8 9]
拓展:(2)特别须知:使用 copy 函数要注意对于 copy(dst, src),要初始化 dst 的 size,否则无法复制。
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// 错误示例:
dst := make([]int, 0)
src := []int{1, 2, 3}
copy(dst, src)
printSlice(src)
printSlice(dst)
//输出结果:
//len=3 cap=3 slice=[1 2 3]
//len=0 cap=0 slice=[]
// 正确示例:
dst := make([]int, 3) // 令size=3
src := []int{1, 2, 3}
copy(dst, src)
printSlice(src)
printSlice(dst)
//输出结果:
//len=3 cap=3 slice=[1 2 3]
//len=3 cap=3 slice=[1 2 3]
拓展:(3)dst的len初始化为多少,执行copy,就能拷贝多少(超出src范围的copy零值)。当切片有足够大小的时候,append操作是非常快的。但是当给出初始大小后,我们得到的实际上是一个含有这个size数量切片类型的空元素,直接append肯定不可取。此时如果我们既想有好的效率,又想继续使用append函数而不想区分是否有空的元素,此时就要请出make的第三个参数,容量,也就是我们通过传递给make,0的大小和足够大的容量数值就行了。
// 案例1
var ss=make([]int,10);
ss=append(ss,111);
fmt.Println("1after append",ss, len(ss), cap(ss))
var ss2=make([]int,0, 10);
ss2=append(ss2,111);
fmt.Println("2after append",ss2, len(ss2), cap(ss2))
// 输出
1after append [0 0 0 0 0 0 0 0 0 0 111] 11 20
2after append [111] 1 10
// 案例2
var sa = make ([]int,0);
for i:=0;i<10;i++{
sa=append(sa,i)
}
var da =make([]int,0,10);
var cc=0;
cc = copy(da,sa);
fmt.Printf("copy to da(len=%d)\tda(cap=%d)\t%v\n",len(da),cap(da),da)
da = make([]int,12, 20)
cc=copy(da,sa);
fmt.Printf("copy to da(len=%d)\tda(cap=%d)\tcopied=%d\t%v\n",len(da),cap(da),cc,da)
da = make([]int,10)
cc =copy(da,sa);
fmt.Printf("copy to da(len=%d)\tda(cap=%d)\tcopied=%d\t%v\n",len(da),cap(da),cc,da)
// 输出
copy to da(len=0) da(cap=10) []
copy to da(len=12) da(cap=20) copied=10 [0 1 2 3 4 5 6 7 8 9 0 0]
copy to da(len=10) da(cap=10) copied=10 [0 1 2 3 4 5 6 7 8 9]
6)Q:基于原数组或者切片创建一个新的切片后,那么新的切片的大小和容量是多少呢?
A:对于底层数组容量是 k 的切片 slice[i:j] 来说:
长度: j - i
容量: k - i
// 举个栗子
package main
import "fmt"
func main() {
numbers:=[]int{0,1,2,3,4,5,6,7,8,9,10}
printSlice(numbers)
fmt.Printf("%d\n",numbers[1:3])
fmt.Printf("%d\n",numbers[2:7])
fmt.Printf("%d\n",numbers[:3])
fmt.Printf("%d\n",numbers[4:])
number1:=make([]int,0,5)
number2:=numbers[:3]
printSlice(number1)
printSlice(number2)
number3:=numbers[2:5]
printSlice(number3) //capacity 为 9 是因为 number3 的 ptr 指向第三个元素, 后面还剩 2,3,4,5,6,7,8,9,10, 所以 cap=9。
number4:=numbers[3:8]
printSlice(number4)
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// 运行结果
len=11 cap=11 slice=[0 1 2 3 4 5 6 7 8 9 10]
[1 2]
[2 3 4 5 6]
[0 1 2]
[4 5 6 7 8 9 10]
len=0 cap=5 slice=[]
len=3 cap=11 slice=[0 1 2]
len=3 cap=9 slice=[2 3 4]
len=5 cap=8 slice=[3 4 5 6 7]
7)slice和array作为函数参数的情况1
package main
import "fmt"
func main(){
changeSliceTest()
}
func changeSliceTest() {
arr1 := []int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 3}
fmt.Println("before change arr1, ", arr1)
changeSlice(arr1) // slice 按引用传递
fmt.Println("after change arr1, ", arr1)
fmt.Println("before change arr2, ", arr2)
changeArray(arr2) // array 按值传递
fmt.Println("after change arr2, ", arr2)
fmt.Println("before change arr3, ", arr3)
changeArrayByPointer(&arr3) // 可以显式取array的 指针
fmt.Println("after change arr3, ", arr3)
}
func changeSlice(arr []int) {
arr[0] = 9999
}
func changeArray(arr [3]int) {
arr[0] = 6666
}
func changeArrayByPointer(arr *[3]int) {
arr[0] = 6666
}
// 运行结果
before change arr1, [1 2 3]
after change arr1, [9999 2 3]
before change arr2, [1 2 3]
after change arr2, [1 2 3]
before change arr3, [1 2 3]
after change arr3, [6666 2 3]
slice作为函数参数的情况2
当把 slice 作为参数进行值传递,本身传递的是值,但就其内容 byte* array,实际传递的是引用,所以可以在函数内部修改;但如果对 slice 做 append,而且导致 slice 进行了扩容,实际扩容的是位于函数内复制的一份切片,对于函数外面的切片本身是不会构成变化的。解决办法是传递切片指针。
package main
import (
"fmt"
"unsafe"
)
func main() {
slice_test := []int{1, 2, 3, 4, 5}
fmt.Println(unsafe.Sizeof(slice_test))
fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
slice_value(slice_test)
fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
slice_ptr(&slice_test)
fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
fmt.Println(unsafe.Sizeof(slice_test))
}
func slice_value(slice_test []int) {
slice_test[1] = 100 // 函数外的slice确实有被修改
slice_test = append(slice_test, 6) // 函数外的不变
fmt.Printf("slice_value:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
}
func slice_ptr(slice_test *[]int) { // 这样才能修改函数外的slice
*slice_test = append(*slice_test, 7)
fmt.Printf("slice_ptr:%#v,%#v,%#v\n", *slice_test, len(*slice_test), cap(*slice_test))
}
// 运行结果:
24
main:[]int{1, 2, 3, 4, 5},5,5
slice_value:[]int{1, 100, 3, 4, 5, 6},6,10
main:[]int{1, 100, 3, 4, 5},5,5
slice_ptr:[]int{1, 100, 3, 4, 5, 7},6,10
main:[]int{1, 100, 3, 4, 5, 7},6,10
24