GO Slice(切片)
感觉切片只要知道底层是引用的一个数组对象,就挺好理解了.这里写下一些笔记,方便记忆和以后再来查找.
切片和数组
切片由三个部分组成:指针(指向底层数组),长度(当前切片使用的长度),容量(切片能包含多少个成员)
然后还有一句和数组相关的:当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,并不是原始调用的变量。(所以数组作为参数,是低效的,还需要进行一次数组的拷贝,可以使用数组指针)
然后如果我们想要传递给一个函数一个数组,函数需要对数组进行修改,我们必须使用数组指针(用return当然也不是不行啦?)
但是切片就不需要,来个例子:
func Test_Func(t *testing.T) {
var arr = [...]int{1, 2, 3}
var slice = []int{1, 2, 3}
ModifyArray(arr)
ModifySlice(slice)
t.Logf("%v %v\n", arr, slice)
assert.NotEqual(t, arr[2], 1)
assert.Equal(t, slice[2], 1)
}
func ModifyArray(arr [3]int) {
println(arr)
arr[2] = 1
}
func ModifySlice(slice []int) {
println(slice)
slice[2] = 1
}
凭啥切片就行,大家明明都长得都差不多.
前面说了,切片是由三个部分组成,然后数组传入的是数组的副本.其实我觉得go里所有的类型传入的都是对应的副本,切片也是,指针也是的(值传递).
那都是副本拷贝,那么咋切片可以修改?
切片是由:数组指针,长度,容量组成的
,来划一下重点.
副本传的也是上面这些东西.然后修改切片的时候呢,实际上是通过切片里面的数组指针去修改了,并没有修改切片的值(数组指针,长度,容量).
等看完下面再写另外一个情况,在函数里面,给切片增加成员,会怎么样?
切片
定义一个数组和定义一个切片的区别是[...]和[](当然还有其他的定义方式)
func Test_DefineSlice(t *testing.T) {
var arr = [...]int{1, 2, 3}
var slice1 = []int{1, 2, 3}
var slice2 = make([]int, 3)
var slice3 = arr[:]
fmt.Printf("arr type=%v len=%d cap=%d\n", reflect.TypeOf(arr).String(), len(arr), cap(arr))
fmt.Printf("slice1 type=%v len=%d cap=%d\n", reflect.TypeOf(slice1).String(), len(slice1), cap(slice1))
fmt.Printf("slice2 type=%v len=%d cap=%d\n", reflect.TypeOf(slice2).String(), len(slice2), cap(slice2))
fmt.Printf("slice3 type=%v len=%d cap=%d\n", reflect.TypeOf(slice3).String(), len(slice3), cap(slice3))
}
//Result:
//arr type=[3]int len=3 cap=3
//slice1 type=[]int len=3 cap=3
//slice2 type=[]int len=3 cap=3
//slice3 type=[]int len=3 cap=3
上面方法中的切片是会自动创建一个底层数组,如果切片直接引用一个创建好了的数组呢?
我的猜想是在切片里面修改值,原数组也会跟着一起变(切片指针指向的就是这一个数组)
然后我想再验证一下,如果我的切片再增加一个成员(超出数组限制),那么还会变化吗?
我的猜想是会重新分配到另外一个数组去,然后导致引用的数组不会发生改变(切片指针指向的已经是另外一个数组了)
func Test_Modify(t *testing.T) {
arr := [...]int{1, 2, 3, 4, 5, 6, 7}
slice := arr[:]
slice[4] = 8
t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4])
assert.Equal(t, slice[4], arr[4])
slice = append(slice, 9)
slice[5] = 10
t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4])
assert.Equal(t, slice[4], arr[4])
t.Logf("arr[5]=%v,slice[5]=%v\n", arr[5], slice[5])
assert.NotEqual(t, slice[5], arr[5])
}
验证通过^_^
再来试试两个切片共享一个数组
func Test_ModifyTwoSlice(t *testing.T) {
arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
slice1 := arr[1:5]
slice2 := arr[3:8]
slice1[2] = 8
t.Logf("%v %v %v\n", arr, slice1, slice2)
assert.Equal(t, slice1[2], slice2[0], arr[3])
}
一样的全部一起修改成功了
append
然后我们来看看append
func Test_Append(t *testing.T) {
slice := []int{1, 2, 3}
println(slice)
slice = append(slice, 1)
println(slice)
slice = append(slice, 1)
println(slice)
}
// Result:
// slice type=[]int len=3 cap=3
// [3/3]0xc00005e3e0
// slice type=[]int len=4 cap=6
// [4/6]0xc00008c060
// slice type=[]int len=5 cap=6
// [5/6]0xc00008c060
当容量够的时候切片的内存地址没有发生变化,不够的时候进行了扩容,地址改变了.
刚刚写的时候发现了一个问题,就是每次扩容的大小,我写了一个循环来展示
func Test_Cap(t *testing.T) {
slice1 := []int{1, 2, 3}
slice2 := make([]int, 3, 3)
last := [2]int{0, 0}
for i := 0; i < 100; i++ {
slice1 = append(slice1, 1)
slice2 = append(slice2, 1)
if last[0] != cap(slice1) {
println(slice1)
last[0] = cap(slice1)
}
if last[1] != cap(slice2) {
println(slice2)
last[1] = cap(slice2)
}
}
}
好吧,我以为扩容的容量,如果不是make的话是按照前一次容量的两倍来扩容的,是make就是每次增加的容量是固定的,事实证明我想多了
End
再回到最开始,在函数里面,增加切片的成员.我想应该有了答案.
func ModifySlice(slice []int) {
slice[2] = 1
slice = append(slice, 4)
slice[2] = 3
}
我把之前的ModifySlice
方法修改了一下,然后成员没加,后面再修改回去为3也没有发生变化了.
这是因为append的时候因为容量不够扩容了,导致底层数组指针发生了改变,但是传进来的切片是外面切片的副本,修改这个切片里面的数组指针不会影响到外面的切片
奇淫巧技
例如这个(go圣经里面抄的233),reverse的参数是切片,但是我们需要处理的是一个数组,这里我们就可以直接用arr[:]
把数组转化为切片进行处理
func Test_Demo(t *testing.T) {
arr := [...]int{1, 2, 3, 4}
reverse(arr[:])
t.Logf("%v\n", arr)
}
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
暂时只想到这些,再有的话,以后继续补.如有错误希望指教.
文章评论