Go语言中slice和数组是非常像的两种数据结构,但是切片(slice)比数组更好用,Go更推荐slice。当然在面试中也是出现频率非常高的,总结一些数组和slice常见的问题。
切片(slice)结构的本质对数组的封装,都可以通过下标来访问单个元素。 数组是定长,定义好长度就不能再改变,不同的长度代表不同的类型 数组是一片连续的内存 切片可以动态扩容,类型与长度无关 切片实际上是一个结构体,包含三个字段:长度、容量,底层数组
//数组
array := [3]int{1, 2, 3}
//切片
slice := []int{1, 2, 3}
//切片底层结构
//src/runtime/slice.go
type slice struct {
array unsafe.Pointer //指向底层数组
len int //长度
cap int //容量
}
nil slice:声明为切片,但是没有分配内存,切片的指针是nil。
//只有声明的切片才会产生nil切片,而且还没有分配内存
var slice []int
var slice = *new([]int)
空 slice:切片指针指向了一个数组内存地址,但是数组是空的, 空切片有两种方式产生。
//len和cap都为0
s1 := []int{} //1.空切片,没有任何元素
s2 := make( []int, 0) //2.make 切片,没有任何元素
截取是创建切片的一种常见方式,可以从数据或者slice直接截取,需要指定起止索引位置。 基于已有的数组或者slice进行创建新的slice对象时。新slice和老slice共用底层数据,因此对底层数组的更改都会影响到彼此。前提是两者共用底层数组,如果因为执行append操作使得slice的底层数组发生了扩容,两者就就不会相互影响了,关键点就是是否共用了底层数组
截取的方式:
data := [...]int{1,2,3,4,5}
//1:low(开始位置) 2:high(结束位置) 4:max(容量)
slice := data[1:2:4]
那么low、high、max之间有怎么样的关系呢?首先data可以是slice也可以是数组。low是低索引值,是闭区间,也就是第一个元素的索引下标位置是low:而high则是开区间,什么是开区间呢,就是不包含这个值,比如最后一个元素只能是high-1出的下标元素值。而容量大小为max-1。 满足以下关系:
make和new的函数形式如下
func make(t Type, size ...IntegerType) Type
func new(Type) *Type
相同点:Go语言用来分配内存的函数 不同点: 1:适用的类型不同:make适用于给slice、map、channel分配内存,new适用于int类型、数组、结构体等值类型 2:返回类型不同:make返回一个值,new返回一个指向变量指针 3:make分配空间后会进行初始化, new分配的空间会被清零
//make创建一个长度为0,容量为5的int类型切片
s := make([]int, 0 ,5)
//new分配一个零值得int型
a = new(int)
*a = 5
我们知道Go的切片(slice)是引用类型的值,它有以下一些特性。
通过几个场景来感受下不同情况下slice作为函数参数值的变化。
slice1 := []int{1, 2, 3, 4, 5}
fmt.Printf("slice1: %v\n", slice1)
fmt.Printf("address of slice1: %p %p\n", slice1, &slice1)
// 重置数据
resetSlice(slice1)
fmt.Printf("slice1 after reset: %v\n", slice1)
fmt.Printf("address of slice1 after reset: %p %p\n", slice1, &slice1)
//重置函数
func resetSlice(slice2 []int) {
for i := 0; i < len(slice2); i++ {
slice2[i] = slice2[i] + i*10
}
fmt.Printf("address of weight: %p %p\n\n", slice2, &slice2)
}
上面的输出结果:
slice1: [1 2 3 4 5]
address of slice1: 0xc000088630 0xc000094860
address of slice2: 0xc000088630 0xc0000948c0
slice2 after reset: [1 12 23 34 45]
slice1 after reset: [1 12 23 34 45]
address of slice1 after reset: 0xc000088630 0xc000094860
可以看出函数中修改了slice2的值,原来的slice1的值也改变了。因为函数参数传递时,实际上传递的是引用指向的地址。函数实际上另外开辟了一个临时变量weight来存放这个引用的值,新变量的地址是0xc0000948c0。而实际修改的是slice2指向的地址0xc000088630存放的值,所以可以看到slice1也改变了。
myWeight := make([]int, 1, 3)
fmt.Printf("myWeight: %v\n", myWeight)
fmt.Printf("address of myWeight: %p %p\n", myWeight, &myWeight)
fmt.Printf("myWeight len: %v, cap: %v\n\n", len(myWeight), cap(myWeight))
// 添加数据
addWeightRecord(myWeight)
fmt.Printf("myWeight after add: %v\n", myWeight)
fmt.Printf("address of myWeight after add: %p %p\n", myWeight, &myWeight)
fmt.Printf("myWeight len: %v, cap: %v\n", len(myWeight), cap(myWeight))
//添加数据函数
func addWeightRecord(weight []int) {
weightCap := cap(weight)
weight[0] = 10
fmt.Printf("cap of weight: %v\n\n", weightCap)
for i := 0; i < weightCap-1; i++ {
weight = append(weight, i)
}
fmt.Printf("weight: %v\n", weight)
fmt.Printf("address of weight: %p %p\n", weight, &weight)
fmt.Printf("weight len: %v, cap: %v\n", len(weight), cap(weight))
}
上面输出结果
myWeight: [0]
address of myWeight: 0xc0001acaa0 0xc0001a68c0
myWeight len: 1, cap: 3
cap of weight: 3
weight: [10 0 1]
address of weight: 0xc0001acaa0 0xc0001a6920
weight len: 3, cap: 3
myWeight after add: [10]
address of myWeight after add: 0xc0001acaa0 0xc0001a68c0
myWeight len: 1, cap: 3
先看现象:函数中修改slice中的变量值后,外部slice也同样进行了修改。但是向slice中append添加元素时,外部的slice并未进行添加元素,那么这又是什么原因导致的呢? 我们知道slice的底层是由指向数组的指针、len、cap组成的结构体,对第一个元素的修改,内外的slice的第一个元素值都是10,因为内外slice都是指向的slice的地址0xc0001acaa0。但是append的时候weight的len会变化为3,cap不会变(未超过容量)。但是myWeight的len不会变,所以只能读到第一个元素值10。
接着场景2,如果append对应的循环weightCap-1改成6,会发现函数内部出现了扩容。slice中对应的array指向的地址会发生变化,是两个不同的slice.
slice切片的扩容对于append向slice添加元素时,假如容量cap够用,追加新元素进去,slice的len会增加。如果容量不够,则slice先进行扩容得到新的slice,然后将元素追加到新slice。
当切片slice容量小于1024时,将以两倍速度进行扩容,也就是新的slice容量将会是原slice容量的两倍。当切片容量较大时(原slice的容量大于等于1024),为了避免空间浪费,将采用较小的扩容倍速(新的扩容将大于等于原来1.25倍)。其实很多网上的总结是1.25倍,但是在实际扩容时需要考虑内存对齐,所以扩容时大于等于1.25倍的。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。