GO语言:slice切片
注意:在go语言中,切片属于引用类型,数组属于数值类型
var arr = [3]int{1, 2, 3}
var slice = []int{1, 2, 3}
temp1 := arr
fmt.Printf("arr:%p temp1:%p\n", &arr, &temp1)
temp2 := slice
fmt.Printf("slice:%p temp2:%p\n", slice, temp2)
arr:0xc00000a3e0 temp1:0xc00000a420
slice:0xc00000a400 temp2:0xc00000a400
可以看到arr与temp1地址不同,而slice与temp2地址相同
试图更改temp1,temp2中的数据
temp1[0] = 100
temp2[0] = 100
fmt.Printf("arr:%v temp1:%v\n", arr, temp1)
fmt.Printf("slice:%v temp2:%v\n", slice, temp2)
arr:[1 2 3] temp1:[100 2 3]
slice:[100 2 3] temp2:[100 2 3]
数组arr中的元素值并没有改变,而切片slice中的元素值发生了变化,更加证明:
在go语言中,数组是数值类型,切片是引用类型
如何得到切片
1.直接定义
// 切片的定义
// 数组是数值类型,切片是引用类型
var s1 []int // 定义一个存放int类型元素的切片
var s2 []string
fmt.Println(s1, s2)
// 初始化
// 底层都是创建数组,封装成切片返回的,所以长度和容量相等
s1 = []int{1, 2, 3}
s2 = []string{"北京", "上海", "深圳"}
定义切片后,未初始化时切片指针指向为nil
该种方式其实是在底层创建了数组,进行切片封装后返回的,所以切片的长度和容量相等
2.由数组得到切片
// 由数组得到切片
a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
// 左闭右开
s3 := a1[0:4]
fmt.Println(s3)
s4 := a1[1:6]
fmt.Println(s4)
s5 := a1[:4]
fmt.Println(s5)
s6 := a1[3:]
fmt.Println(s6)
s7 := a1[:]
fmt.Println(s7)
[1 3 5 7]
[3 5 7 9 11]
[1 3 5 7]
[7 9 11 13]
[1 3 5 7 9 11 13]
采用左闭右开的原则对底层的数组进行切分
切片指针指向对应底层数组元素基址
fmt.Printf("a1[0]:%p s3:%p\n", &a1[0], s3)
fmt.Printf("a1[1]:%p s4:%p\n", &a1[1], s4)
fmt.Printf("a1:%p s5:%p\n", &a1, s5)
fmt.Printf("a1[3]:%p s6:%p\n", &a1[3], s6)
fmt.Printf("a1:%p s7:%p\n", &a1, s7)
a1[0]:0xc00000e2c0 s3:0xc00000e2c0
a1[1]:0xc00000e2c8 s4:0xc00000e2c8
a1:0xc00000e2c0 s5:0xc00000e2c0
a1[3]:0xc00000e2d8 s6:0xc00000e2d8
a1:0xc00000e2c0 s7:0xc00000e2c0
可以看到,切片地址与对应底层数组元素的基地相同
len()求切片长度 cap()求切片容量
// 切片的容量是指切片指针指向底层数组元素到底层数组最后一个元素的元素个数
fmt.Printf("len(s3):%d cap(s3):%d\n", len(s3), cap(s3))
fmt.Printf("len(s4):%d cap(s4):%d\n", len(s4), cap(s4))
fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5))
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6))
fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7))
len(s3):4 cap(s3):7
len(s4):5 cap(s4):6
len(s5):4 cap(s5):7
len(s6):4 cap(s6):4
len(s7):7 cap(s7):7
切片长度:切片中元素个数,即为切片长度
切片容量:指切片指针指向底层数组的元素到底层数组最后一个元素的元素个数(感觉比较拗口)
这里引用两张图
出处:https://www.liwenzhou.com/posts/Go/06_slice/
这两张图已经将切片解释的非常清楚。
3.切片再切片
// 切片再切片
s8 := s6[3:]
fmt.Println(s8)
[13]
底层数组仍然相同
4.make()函数创建切片
// make()函数创建切片
s9 := make([]int, 5)
s10 := make([]int, 5, 10)
fmt.Printf("s9:%v len(s9):%d cap(s9):%d\n", s9, len(s9), cap(s9))
fmt.Printf("s10:%v len(s10):%d cap(s10):%d\n", s10, len(s10), cap(s10))
s9:[0 0 0 0 0] len(s9):5 cap(s9):5
s10:[0 0 0 0 0] len(s10):5 cap(s10):10
make(类型,长度,容量)
若不指定容量,则与长度一样
切片不能比较
切片是引用类型,存的是指向底层数组的基址,比较没有任何意义
跟nil比较我们可以知道切片是否已经指向一个底层数组,是否已经申请到了一块内存空间
如果切片定义且为初始化,那么切片为nil
一个nil值的切片长度和容量一定为0,但是切片长度和容量为0的切片不一定为nil
var s12 []int
fmt.Println(s12 == nil)
fmt.Printf("s12:%v len(s12):%d cap(s12):%d\n", s12, len(s12), cap(s12))
var s13 = make([]int, 0)
fmt.Println(s13 == nil)
fmt.Printf("s13:%v len(s13):%d cap(s13):%d\n", s13, len(s13), cap(s13))
s12声明切片,未初始化,切片指向底层数组指针为nil,那么长度和容量一定为0
s13通过make函数创建,切片指向一个底层数组,底层数组长度为0
s13的常见过程类似于:
var arr2 = [0]int{}
s14 := arr2[:]
fmt.Println(s14 == nil)
fmt.Printf("s14:%v len(s14):%d cap(s14):%d\n", s14, len(s14), cap(s14))
false
s14:[] len(s14):0 cap(s14):0
append()
切片增加元素使用append()函数追加元素
var s15 = []int{1, 2, 3, 4}
fmt.Printf("s15:%p\n", s15)
fmt.Printf("s15:%v len(s15):%d cap(s15):%d\n", s15, len(s15), cap(s15))
s15 = append(s15, 5)
fmt.Printf("s15:%p\n", s15)
fmt.Printf("s15:%v len(s15):%d cap(s15):%d\n", s15, len(s15), cap(s15))
s15:0xc00009e2a0
s15:[1 2 3 4] len(s15):4 cap(s15):4
s15:0xc0000b62c0
s15:[1 2 3 4 5] len(s15):5 cap(s15):8
切片发生了扩容,而s15在append前后,地址发生了变化,可以推测出底层是重新分配一块更大的内存空间来供我们使用,类似于仓库搬迁扩容
var s16 = make([]int, 2, 4)
s16[0] = 1
s16[1] = 2
fmt.Printf("s16:%p\n", s16)
fmt.Printf("s16:%v len(s16):%d cap(s16):%d\n", s16, len(s16), cap(s16))
s16 = append(s16, 3)
fmt.Printf("s16:%p\n", s16)
fmt.Printf("s16:%v len(s16):%d cap(s16):%d\n", s16, len(s16), cap(s16))
s16:0xc00000a560
s16:[1 2] len(s16):2 cap(s16):4
s16:0xc00000a560
s16:[1 2 3] len(s16):3 cap(s16):4
既然仓库搬迁扩容需要切换仓库地址,那自然的,如果不发生仓库搬迁扩容的话,原本的东西当然也就继续放在同一个位置,所以切片指向底层数组基址不会发生变化。
而切片的扩容策略,一般在切片较小时,每次扩容以2倍形式扩容。
切片元素删除
go语言中没有专门用于删除切片元素的函数
但可以借用append()函数来实现
// 切片元素删除
arr3 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s17 := arr3[:]
fmt.Println("arr3:", arr3)
s17 = append(s17[:1], s17[2:]...)
fmt.Println("s17:", s17)
fmt.Println("arr3:", arr3)
arr3: [1 2 3 4 5 6 7 8 9 10]
s17: [1 3 4 5 6 7 8 9 10]
arr3: [1 3 4 5 6 7 8 9 10 10]
注:s17[3:]… 其中…表示拆分元素,逐个追加
对于arr3数组的变化:切片相当于一个选择框,只能选择连续的空间(元素),所以,实际上并不是对元素进行剔除,而是将底层数组的元素整体进行移动,让切片看起来好像去除了某个元素。