2019独角兽企业重金招聘Python工程师标准>>>
深入数组&&切片
数组的值传递
在 Go 中,与 C 数组变量隐式作为指针使用不同,Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。
package slice
import (
"fmt"
"testing"
)
func Test_array_pointer(t *testing.T) {
arrayA := [2]int{100, 200}
var arrayB [2]int
arrayB = arrayA
fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)
testArray(arrayA)
}
func testArray(x [2]int) {
fmt.Printf("funcArray : %p , %v\n", &x, x)
}
打印输出,
arrayA : 0xc000014310 , [100 200]
arrayB : 0xc000014320 , [100 200]
funcArray : 0xc000014350 , [100 200]
arrayA数组赋值给 arrayB ,把arrayA 作为参数传递给方法testArray ,这两个操作都发生了值传递,都会复制整个数组数据。通过打印输出结果也可以看到,arrayA、arrayB、funcArray 的内存地址都不相同,这就是数组通过值传递的结果。
数组通过指针传递
看下面这个例子,
func Test_array_pointer(t *testing.T) {
arrayA := [2]int{100, 200}
testArrayPoint(&arrayA) // 1.传数组指针
fmt.Printf("arrayA : %p \t %v\n", &arrayA, arrayA)
}
func testArrayPoint(x *[2]int) {
fmt.Printf("funcArray : %p \t %v\n", x, *x)
(*x)[1] += 100
}
打印输出,
funcArray : 0xc000014310 [100 200]
arrayA : 0xc000014310 [100 300]
通过打印可以看出,通过指针传递数组在方法内的修改会影响到原来的数组。
切片的数据结构
SliceHeader是Slice运行时的具体表现,它的结构定义如下:
SliceHeader is the runtime representation of a slice.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
正好对应Slice的三要素,Data
指向具体的底层数据源数组,Len
代表长度,Cap
代表容量。
切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。
切片的数据结构之 - make创建切片
上图是用 make 函数创建的一个 len = 4, cap = 6 的切片。内存空间申请了一个长度为6的匿名数组空间。由于 len = 4,所以后面2个暂时访问不到,但是容量还是在的。这时候数组里面每个变量都是0 。
切片的数据结构之 - 字面量创建切片
用字面量创建的一个 len = 6,cap = 6 的切片,这时候数组里面每个元素的值都初始化完成了。需要注意的是 [ ] 里面不要写数组的容量,因为如果写了个数以后就是数组了,而不是切片了。
切片的数据结构之 - 使用数组创建切片
Slice A 创建出了一个 len = 3,cap = 3 的切片。从原数组的第二位元素(0是第一位)开始切,一直切到第四位为止(不包括第五位)。同理,Slice B 创建出了一个 len = 2,cap = 4 的切片。
Full slice expression 见:https://my.oschina.net/xinxingegeya/blog/672519#h3_14
切片的指针
在Go的源码中,slice 对应的是一个结构体,
type slice struct {
array unsafe.Pointer
len int
cap int
}
这个结构体有3个字段,第一个字段表示array的指针,指向切片底层数据源数组的指针,第二个是表示slice的长度,第三个是表示slice的容量,注意:len和cap都不是指针。
当slice作为参数传递时,传递的是slice值的拷贝。定义一个函数如下,
func test1(slice_2 []int) {
}
函数外的slice叫slice_1, 函数的参数叫slice_2,当函数传递slice_1的时候,其实传入的是slice_1参数值的拷贝。把 slice 看成一个结构体更好理解,test1 函数接收 slice_1 结构体值的拷贝作为参数, 因此结构体slice_1 和 slice_2 的指针肯定是不一样的,但slice_1 和 slice_2 两个切片底层的数据源数组指针是一样的。如下代码,
package slice
import (
"fmt"
"reflect"
"testing"
"unsafe"
)
func Test_slice_pointer(t *testing.T) {
slice_1 := make([]int, 10, 20)
var pSlice_1 *[]int = &slice_1
pSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(pSlice_1))
fmt.Printf("切片底层数据源数组的指针-1 =%p\n", slice_1)
fmt.Printf("切片底层数据源数组的指针-2 =%p\n", &slice_1[0])
fmt.Printf("切片底层数据源数组的指针-3 =%d\n", uintptr(unsafe.Pointer(&slice_1[0])))
fmt.Printf("切片底层数据源数组的指针-4 =%d\n", pSliceHeader.Data)
fmt.Printf("切片作为结构体的指针 =%p\n", pSlice_1)
fmt.Printf("切片作为结构体的指针 =%p\n", pSliceHeader)
fmt.Println("===============")
test1(slice_1)
fmt.Println("===============")
test2(pSlice_1)
}
func test1(slice_2 []int) {
var pSlice_2 *[]int = &slice_2
pSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(pSlice_2))
fmt.Printf("test1-切片底层数据源数组的指针-1 =%p\n", slice_2)
fmt.Printf("test1-切片底层数据源数组的指针-2 =%p\n", &slice_2[0])
fmt.Printf("test1-切片底层数据源数组的指针-3 =%d\n", uintptr(unsafe.Pointer(&slice_2[0])))
fmt.Printf("test1-切片底层数据源数组的指针-4 =%d\n", pSliceHeader.Data)
fmt.Printf("test1-切片作为结构体的指针 =%p\n", pSlice_2)
fmt.Printf("test1-切片作为结构体的指针 =%p\n", pSliceHeader)
}
func test2(slice_2 *[]int) { //这样才能修改函数外的slice
fmt.Printf("test2-切片作为结构体的指针 =%p\n", slice_2)
}
打印输出,
=== RUN Test_slice_pointer
切片底层数据源数组的指针-1 =0xc0000bc000
切片底层数据源数组的指针-2 =0xc0000bc000
切片底层数据源数组的指针-3 =824634490880
切片底层数据源数组的指针-4 =824634490880
切片作为结构体的指针 =0xc00000a0c0
切片作为结构体的指针 =0xc00000a0c0
===============
test1-切片底层数据源数组的指针-1 =0xc0000bc000
test1-切片底层数据源数组的指针-2 =0xc0000bc000
test1-切片底层数据源数组的指针-3 =824634490880
test1-切片底层数据源数组的指针-4 =824634490880
test1-切片作为结构体的指针 =0xc00000a100
test1-切片作为结构体的指针 =0xc00000a100
===============
test2-切片作为结构体的指针 =0xc00000a0c0
--- PASS: Test_slice_pointer (0.00s)
PASS
可见,结构体slice_1 和 slice_2 的指针肯定是不一样的,但slice_1 和 slice_2 两个切片底层的数据源数组指针是一样的。
切片作为参数&切片的指针作为参数
首先来看下面一段代码,
package slice
import (
"fmt"
"testing"
)
func Test_change_slice(t *testing.T) {
slice_1 := []int{1, 2, 3, 4, 5}
fmt.Printf("Test_change_slice-01-->data:\t%#v\n", slice_1)
fmt.Printf("Test_change_slice-02-->len:\t%#v\n", len(slice_1))
fmt.Printf("Test_change_slice-03-->cap:\t%#v\n", cap(slice_1))
test001(slice_1)
fmt.Printf("Test_change_slice-04-->data:\t%#v\n", slice_1)
fmt.Printf("Test_change_slice-05-->len:\t%#v\n", len(slice_1))
fmt.Printf("Test_change_slice-06-->cap:\t%#v\n", cap(slice_1))
test002(&slice_1)
fmt.Printf("Test_change_slice-07-->data:\t%#v\n", slice_1)
fmt.Printf("Test_change_slice-08-->len:\t%#v\n", len(slice_1))
fmt.Printf("Test_change_slice-09->cap:\t%#v\n", cap(slice_1))
fmt.Println("===================")
Test_change_slice02(t)
}
func Test_change_slice02(t *testing.T) {
slice_1 := make([]int, 10, 20)
fmt.Printf("Test_change_slice-11-->data:\t%#v\n", slice_1)
fmt.Printf("Test_change_slice-12-->len:\t%#v\n", len(slice_1))
fmt.Printf("Test_change_slice-13-->cap:\t%#v\n", cap(slice_1))
test001(slice_1)
fmt.Printf("Test_change_slice-14-->data:\t%#v\n", slice_1)
fmt.Printf("Test_change_slice-15-->len:\t%#v\n", len(slice_1))
fmt.Printf("Test_change_slice-16-->cap:\t%#v\n", cap(slice_1))
test002(&slice_1)
fmt.Printf("Test_change_slice-17-->data:\t%#v\n", slice_1)
fmt.Printf("Test_change_slice-18-->len:\t%#v\n", len(slice_1))
fmt.Printf("Test_change_slice-19-->cap:\t%#v\n", cap(slice_1))
}
func test001(slice_2 []int) {
slice_2[1] = 6666 //函数外的slice确实有被修改
slice_2 = append(slice_2, 8888) //函数外的不变
fmt.Printf("test001-->data:\t%#v\n", slice_2)
fmt.Printf("test001-->len:\t%#v\n", len(slice_2))
fmt.Printf("test001-->cap:\t%#v\n", cap(slice_2))
}
func test002(slice_2 *[]int) { //这样才能修改函数外的slice
*slice_2 = append(*slice_2, 6666)
}
打印输出,
=== RUN Test_change_slice
Test_change_slice-01-->data: []int{1, 2, 3, 4, 5}
Test_change_slice-02-->len: 5
Test_change_slice-03-->cap: 5
test001-->data: []int{1, 6666, 3, 4, 5, 8888}
test001-->len: 6
test001-->cap: 10
Test_change_slice-04-->data: []int{1, 6666, 3, 4, 5}
Test_change_slice-05-->len: 5
Test_change_slice-06-->cap: 5
Test_change_slice-07-->data: []int{1, 6666, 3, 4, 5, 6666}
Test_change_slice-08-->len: 6
Test_change_slice-09->cap: 10
===================
Test_change_slice-11-->data: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Test_change_slice-12-->len: 10
Test_change_slice-13-->cap: 20
test001-->data: []int{0, 6666, 0, 0, 0, 0, 0, 0, 0, 0, 8888}
test001-->len: 11
test001-->cap: 20
Test_change_slice-14-->data: []int{0, 6666, 0, 0, 0, 0, 0, 0, 0, 0}
Test_change_slice-15-->len: 10
Test_change_slice-16-->cap: 20
Test_change_slice-17-->data: []int{0, 6666, 0, 0, 0, 0, 0, 0, 0, 0, 6666}
Test_change_slice-18-->len: 11
Test_change_slice-19-->cap: 20
--- PASS: Test_change_slice (0.00s)
PASS
函数外的slice叫slice_1, 函数的参数叫slice_2,当函数传递slice_1的时候,其实传入的确实是slice_1参数的拷贝,所以slice_2是slise_1的副本,但要注意的是slice_2里存储的数组的指针,所以当在函数内更改数组内容时,函数外的slice_1的内容也改变了。在函数内用append时,append会自动以倍增的方式扩展slice_2的容量,但是扩展也仅仅是函数内slice_2的长度和容量,slice_1的长度和容量是没变的,所以在函数外打印时看起来就是没变。
当把 slice 作为指针传递时,
func test002(slice_2 *[]int) { //这样才能修改函数外的slice
*slice_2 = append(*slice_2, 6666)
}
才能对外部的slice的长度和容量产生修改。
分片指针作为方法的接收者
如下代码,
type Slice []int
func (A *Slice) Append001(value int) {
*A = append(*A, value)
fmt.Printf(" A = %v , len(A)=%d , cap(A)=%d \n", *A, len(*A), cap(*A))
}
func Test_append(t *testing.T) {
mSlice := make(Slice, 10, 20)
mSlice.Append001(5)
fmt.Printf(" mSlice = %v , len(mSlice)=%d , cap(mSlice)=%d \n", mSlice, len(mSlice), cap(mSlice))
}
打印输出,
=== RUN Test_append
A = [0 0 0 0 0 0 0 0 0 0 5] , len(A)=11 , cap(A)=20
mSlice = [0 0 0 0 0 0 0 0 0 0 5] , len(mSlice)=11 , cap(mSlice)=20
--- PASS: Test_append (0.00s)
PASS
你会看到程序得到正确的运行,对调用者的分片完成了更改。
slice append方法的返回值
如下代码,
type Slice []int
func (A Slice) changeSlice(value int) (ret Slice) {
ret = append(A, value)
fmt.Printf(" ret = %v , len(ret)=%d , cap(ret)=%d \n", ret, len(ret), cap(ret))
return
}
func Test_change_slice_ret(t *testing.T) {
mSlice := make(Slice, 10, 20)
mSlice = mSlice.changeSlice(666)
fmt.Printf(" mSlice = %v , len(mSlice)=%d , cap(mSlice)=%d \n", mSlice, len(mSlice), cap(mSlice))
}
打印输出,
=== RUN Test_change_slice_ret
ret = [0 0 0 0 0 0 0 0 0 0 666] , len(ret)=11 , cap(ret)=20
mSlice = [0 0 0 0 0 0 0 0 0 0 666] , len(mSlice)=11 , cap(mSlice)=20
--- PASS: Test_change_slice_ret (0.00s)
PASS
========END========