golang slice
1、slice是什么
slice译作“切片”。它非常类似数组,通过下标进行访问。我们都知道slice是建立在数组上的,操作起来比数组更灵活
2、slice源码 (runtime/slice.go)
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
array 就是指向下层数组的指针
len 长度 切片访问元素时,下标不能超过len大小 len长度最大长度也就等于容量
cap 容量 也是slice能扩张到的最大限度。
3、slice的创建
1、直接声明
var s1 []int
2、new
s2 := *new([]int)
3、字面量
s3 := []int{1,3,5,7}
4、make
s4 := make([]int,5,11)
5、从切片或者数组截取
s5 := s4[1:]
4、直接声明
一定注意,此时声明的切片其实是nil切片。它的长度和容量都是0.
验证:
var s1 []int
println(s1 == nil)
此时打印结果为: true
另外还有一种切片叫空切片,empty slice,即长度和容量也都为0,但是其数组指向地址是 0xc42003bda0
empty slice和nil的对比结果为false
var s1 []int
var s2 = []int{}
println(s1 == nil) // 打印结果为:true
println(s2==nil) // 打印结果为: false
官方推荐使用nil切片
5、字面量
var s3 = []int{1,3,5,7,10:98} // 注意10:98表示下标为10 的元素赋值是98
fmt.Println(s3)
// 打印结果是 [1 3 5 7 0 0 0 0 0 0 98]
6、make方式
需要传入类型,长度,容量
s5 := make([]int, 5, 8)
s5[4]=10
fmt.Println(s5[4])
7、从切片或者数组截取
s5 := make([]int, 5, 8)
s6:=s5[:3]
s6[0]=11 // 此时s5 s6共用底层的数组,s5改变了数组的0号索引的数据,
// 所以切片s6获取0号索引得到的数据不再是0,而是11了
fmt.Println(s5[0])// 打印结果 11
fmt.Println(s6[0])// 打印结果 11
8、make和new的拉扯
make()比new()函数多一些操作,new()函数只会进行内存分配并做默认的赋0初始化,而make()可以先为底层数组分配好内存,然后从这个底层数组中再额外生成一个slice并初始化。另外,make只能构建slice、map和channel这3种结构的数据对象,因为它们都指向底层数据结构,都需要先为底层数据结构分配好内存并初始化。
每一个slice结构都由3部分组成:容量(capacity)、长度(length)和指向底层数组某元素的指针,它们各占8字节(1个机器字长,64位机器上一个机器字长为64bit,共8字节大小,32位架构则是32bit,占用4字节),所以任何一个slice都是24字节(3个机器字长)。
Pointer:表示该slice结构从底层数组的哪一个元素开始,该指针指向该元素
Capacity:即底层数组的长度,表示这个slice目前最多能扩展到这么长
Length:表示slice当前的长度,如果追加元素,长度不够时会扩展,最大扩展到Capacity的长度(不完全准确,后面数组自动扩展时解释),所以Length必须不能比Capacity更大,否则会报错
网上很多种解读,但是最终要的一点是:
有两个切片,s2是s1的衍生,但随着s2不断append,如果你append 的数据超过了slice的容量(capacity).那么必然会导致一个新的slice生成,两次之后二者就“脱节”了,之后对一个的元素改动就不能同步到另一个了。此时的slice地址id和原来那个slice不一样。
package main
import "fmt"
func main() {
//orig := make([]int, 4, 7)
orig := []int{1,2,3,4}
fmt.Println("processIt before -->", orig, "cap is ", cap(orig))
ret := processIt(orig)
//processIt(orig)
fmt.Println("processIt after ret-->", ret)
fmt.Println("processIt after orig -->", orig)
}
func processIt(l []int) []int{
fmt.Println("processIt start -->", l)
l[0]=888
//l[0]=100
l = append(l, 100,101,102)
l[6]=999
fmt.Println("processIt end -->", l)
return l
}
输出结果:processIt before --> [1 2 3 4] cap is 4
processIt start --> [1 2 3 4]
processIt end --> [888 2 3 4 100 101 999]
processIt after ret--> [888 2 3 4 100 101 999]
processIt after orig --> [888 2 3 4]