当前位置: 首页 > news >正文

深入数组切片

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

深入数组&&切片

数组的值传递

在 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代表容量。

899aa94b361a97f50cedc11bdb9472f4e40.jpg

切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。

e60a3a01e4feaa907f1cc8213012e4bbdad.jpg

切片的数据结构之 - make创建切片

d9954e302eac033f7a19e298ed0f1aab301.jpg

上图是用 make 函数创建的一个 len = 4, cap = 6 的切片。内存空间申请了一个长度为6的匿名数组空间。由于 len = 4,所以后面2个暂时访问不到,但是容量还是在的。这时候数组里面每个变量都是0 。

切片的数据结构之 - 字面量创建切片

3786f1309ecd41843059dd25bc96b4551f1.jpg

用字面量创建的一个 len = 6,cap = 6 的切片,这时候数组里面每个元素的值都初始化完成了。需要注意的是 [ ] 里面不要写数组的容量,因为如果写了个数以后就是数组了,而不是切片了。

切片的数据结构之 - 使用数组创建切片

50232492a69c47d3d230eea8b5a4d139cb3.jpg

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========

转载于:https://my.oschina.net/xinxingegeya/blog/3018949

相关文章:

  • Spring入门(一):创建Spring项目
  • 如何判断我们的代理ip是高匿
  • Java初学者最佳的学习方法以及会遇到的坑(内含学习资料)!
  • python发送邮件
  • VMware下ubuntu与Windows实现文件共享的方法(zhuan)
  • 接口测试与Postman
  • antiX 17.4 发布,轻量级 Linux 发行版
  • ansible一键部署脚本
  • Android后台任务(HandlerThread、AsyncTask、IntentService)
  • 为什么开发人员必须要了解数据库锁?
  • 前嗅ForeSpider脚本教程:基本语句
  • Redis保证事务一致性,以及常用的数据结构
  • LNMP基础知识及简单搭建(用于个人学习与回顾)
  • Gnu/Linux 链接XServer方法
  • vue中添加favicon.ico
  • JS变量作用域
  • js中的正则表达式入门
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • MySQL QA
  • python3 使用 asyncio 代替线程
  • React+TypeScript入门
  • React的组件模式
  • Spring-boot 启动时碰到的错误
  • 大数据与云计算学习:数据分析(二)
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端临床手札——文件上传
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 学习HTTP相关知识笔记
  • 【云吞铺子】性能抖动剖析(二)
  • ​2020 年大前端技术趋势解读
  • (03)光刻——半导体电路的绘制
  • (1) caustics\
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (16)Reactor的测试——响应式Spring的道法术器
  • (2.2w字)前端单元测试之Jest详解篇
  • (LeetCode) T14. Longest Common Prefix
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)计算机毕业设计ssm电影分享网站
  • (四)模仿学习-完成后台管理页面查询
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)scrum常见工具列表
  • (转载)PyTorch代码规范最佳实践和样式指南
  • ***原理与防范
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET Core 项目指定SDK版本
  • .NET delegate 委托 、 Event 事件
  • .NET Micro Framework初体验(二)
  • .NET Reactor简单使用教程
  • .Net Winform开发笔记(一)
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .NET框架
  • .NET企业级应用架构设计系列之应用服务器
  • .net项目IIS、VS 附加进程调试