深入数组&&切片

原创
2019/03/06 20:56
阅读数 66

深入数组&&切片

数组的值传递

在 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]

通过打印可以看出,通过指针传递数组在方法内的修改会影响到原来的数组。

 

切片的数据结构

slice 共有三个属性: 指针,指向底层数组; 长度,表示切片可用元素的个数,也就是说使用下标对 slice 的元素进行访问时,下标不能超过 slice 的长度; 容量,底层数组的元素个数,容量 >= 长度。

https://golang.org/src/runtime/slice.go

// runtime/slice.go
type slice struct {
	array unsafe.Pointer // 元素指针
	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 的切片。从原数组的第三位元素开始切,一直切到第五位为止(也即是数组的索引为,2、3、4的三个元素,不包括索引为5的元素)。同理,Slice B 创建出了一个 len = 2,cap = 4 的切片。

Full slice expression 见:https://my.oschina.net/xinxingegeya/blog/672519#h3_14

 

切片的指针

在 Go 的反射中就存在一个与之对应的数据结构 SliceHeader

https://golang.org/pkg/reflect/#SliceHeader

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}

当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

append方法的返回值返回了一个新的slice ,新的slice长度和容量都会发生改变,但指向底层数组的指针不会变。changeSlice方法把新的slice返回,我们可以看到返回的slice长度发生了变化和内容都发生了变化。

========END========

展开阅读全文
Go
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部