slice全解析
slice全解析
昨天组内小伙伴做分享,给出了这么一段代码:
package main
import (
"fmt"
)
func fun1(x int) {
x = x + 1
}
func fun2(x *int) {
*x = *x + 1
}
func fun3(x []int) {
x = append(x, 3)
}
func fun4(x *[]int) {
*x = append(*x, 3)
}
func fun5(x [4]int) {
x[3] = 100
}
func fun6(x *[4]int) {
(*x)[3] = 200
}
// 传值,传指针
func main() {
x1 := 10
fmt.Printf("%+v\n", x1)
fun1(x1)
fmt.Printf("%+v\n\n", x1)
fmt.Printf("%+v\n", x1)
fun2(&x1)
fmt.Printf("%+v\n\n", x1)
var x3 []int
x3 = append(x3, 0, 1, 2)
fmt.Printf("%+v\n", x3)
fun3(x3)
fmt.Printf("%+v\n\n", x3)
fmt.Printf("%+v\n", x3)
fun4(&x3)
fmt.Printf("%+v\n\n", x3)
var x4 [4]int
for i := 0; i < 4; i++ {
x4[i] = i
}
fmt.Printf("%+v\n", x4)
fun5(x4)
fmt.Printf("%+v\n\n", x4)
fmt.Printf("%+v\n", x4)
fun6(&x4)
fmt.Printf("%+v\n\n", x4)
}
可以放在play上运行一下,实际输出的是
10
10
10
11
[0 1 2]
[0 1 2]
[0 1 2]
[0 1 2 3]
[0 1 2 3]
[0 1 2 3]
[0 1 2 3]
[0 1 2 200]
得出的结论是:slice是引用传递,数组是值传递,但是要想修改slice和数组,都需要把slice或者数组的地址传递进去。
这个结论中的数组是值传递,要在调用函数内部修改数组值,必须传递数组指针,我没有什么意见。但是slice的部分,却并没有那么简单。基本上,需要明确下面几点才能解释上面的代码。
slice的结构是uintptr+len+cap
比如我定义了一个slice, 不管是什么方法定义的
var a []int
a = make([]int, 1)
a := []int{1,2}
这里的a都是由一个固定的数据结构赋值的

这个数据结构有三个,一个是指向一个定长数组的指针,一个是len,表示我这个slice包含了几个值,还有一个是cap,表示我申请定长数组的时候申请了多大的空间。
slice的append操作是根据cap和len的关系判断是否申请新的空间
在内存看空间中,没有不定长的数组,所有不定长数组的语法都是语言本身封装了。比如golang中的slice。slice可以在初始化的时候就定义好我需要使用多大的空间(cap)
a := make([]int, 1, 10)
这里的10也就是cap,1是len,说明我已经创建了10个int空间给这个slice。
当不断往a中append数据的时候,首先是len不断增加,当len和cap一样的时候,这个时候再append数据,就会新开辟一个数组空间,这个数组空间长度为多大呢?2*cap。
举例说,如果上述的a后续又append了9个数据,这个时候如果再append一个数据,就会发现cap变成20了。
当然,如果扩容了,那么我们说的slice的第一个元素,指向定长数组的地址就会变化。
理解下下面这个代码:
package main
import "fmt"
import "unsafe"
func main() {
var a []int
a = append(a, 0)
printSlice("a", a)
a = append(a, 1)
printSlice("a", a)
a = append(a, 2, 3, 4)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d ptr=%p %v\n",s, len(x), cap(x), unsafe.Pointer(&x[0]), x)
}
输出:
a len=1 cap=2 ptr=0x10414020 [0]
a len=2 cap=2 ptr=0x10414020 [0 1]
a len=5 cap=8 ptr=0x10458020 [0 1 2 3 4]
函数参数是slice的时候传递的是“slice结构”的值拷贝
我们说的slice为参数传递的时候传递是引用传递,实际上,它传递的是Slice结构(uintptr+len+cap)的一个复制,但是由于uintptr对应的是一个定长的数组,所以基本上当slice作为参数传递的时候,返回回来的slice结构是不会变的,对应的定长数组的大小是不会变的,但是这个定长数组里面的具体值是有可能变的。
看下面几个例子:
package main
import (
"fmt"
"unsafe"
)
func fun3(x []int) {
fmt.Printf("%p\n", unsafe.Pointer(&x[0]))
x = append(x, 3)
x[2] = 100
fmt.Printf("%p\n", unsafe.Pointer(&x[0]))
}
func main() {
var x3 []int
x3 = append(x3, 0, 1, 2)
fmt.Printf("%+v\n", x3)
fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))
fun3(x3)
fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))
fmt.Printf("%+v\n\n", x3)
}
输出:
[0 1 2]
0x10414020
0x10414020
0x10414020
0x10414020
[0 1 100]
这里的x3[2]的的值变化了。但是slice的指针地址没有变化。
如果在f3里面修改x[3]的值:
package main
import (
"fmt"
"unsafe"
)
func fun3(x []int) {
fmt.Printf("%p\n", unsafe.Pointer(&x[0]))
x = append(x, 3)
x[3] = 100
fmt.Printf("%p\n", unsafe.Pointer(&x[0]))
}
func main() {
var x3 []int
x3 = append(x3, 0, 1, 2)
fmt.Printf("%+v\n", x3)
fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))
fun3(x3)
fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))
fmt.Printf("%+v\n\n", x3)
}
输出:
[0 1 2]
0x10414020
0x10414020
0x10414020
0x10414020
[0 1 2]
这里的x3的值就不会变化,虽然不会变化,但是实际上slice指向的定长数组的索引为3的值已经变化了。
如果f3是append两个呢?
package main
import (
"fmt"
"unsafe"
)
func fun3(x []int) {
fmt.Printf("%p\n", unsafe.Pointer(&x[0]))
x = append(x, 3, 4)
x[3] = 100
fmt.Printf("%p\n", unsafe.Pointer(&x[0]))
}
func main() {
var x3 []int
x3 = append(x3, 0, 1, 2)
fmt.Printf("%+v\n", x3)
fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))
fun3(x3)
fmt.Printf("%p\n", unsafe.Pointer(&x3[0]))
fmt.Printf("%+v\n\n", x3)
}
输出:
[0 1 2]
0x10414020
0x10414020
0x10458000
0x10414020
[0 1 2]
我们看到在fun3里面append之后的x这个slice指向的地址变化了。但是由于这个x实际上是我们传递进去的x3的值拷贝,所以这个x3并没有被修改。最后输出的时候还是没有变化。
总结
基本上记住了这几个点就明白了slice:
- slice的结构是uintptr+len+cap
- slice的append操作是根据cap和len的关系判断是否申请新的空间
- 函数参数是slice的时候传递的是“slice结构”的值拷贝
参考
https://halfrost.com/go_slice/
slice全解析的更多相关文章
- Go切片全解析
Go切片全解析 目录结构: 数组 切片 底层结构 创建 普通声明 make方式 截取 边界问题 追加 拓展表达式 扩容机制 切片传递的坑 切片的拷贝 浅拷贝 深拷贝 数组 var n [4]int f ...
- Google Maps地图投影全解析(3):WKT形式表示
update20090601:EPSG对该投影的编号设定为EPSG:3857,对应的WKT也发生了变化,下文不再修改,相对来说格式都是那样,可以到http://www.epsg-registry.or ...
- C#系统缓存全解析(转载)
C#系统缓存全解析 对各种缓存的应用场景和方法做了很详尽的解读,这里推荐一下 转载地址:http://blog.csdn.net/wyxhd2008/article/details/8076105
- 【凯子哥带你学Framework】Activity界面显示全解析
前几天凯子哥写的Framework层的解析文章<Activity启动过程全解析>,反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的. 我个人觉得,深入分 ...
- iOS Storyboard全解析
来源:http://iaiai.iteye.com/blog/1493956 Storyboard)是一个能够节省你很多设计手机App界面时间的新特性,下面,为了简明的说明Storyboard的效果, ...
- 【转载】Fragment 全解析(1):那些年踩过的坑
http://www.jianshu.com/p/d9143a92ad94 Fragment系列文章:1.Fragment全解析系列(一):那些年踩过的坑2.Fragment全解析系列(二):正确的使 ...
- (转)ASP.NET缓存全解析6:数据库缓存依赖
ASP.NET缓存全解析文章索引 ASP.NET缓存全解析1:缓存的概述 ASP.NET缓存全解析2:页面输出缓存 ASP.NET缓存全解析3:页面局部缓存 ASP.NET缓存全解析4:应用程序数据缓 ...
- jQuery Ajax 实例 全解析
jQuery Ajax 实例 全解析 jQuery确实是一个挺好的轻量级的JS框架,能帮助我们快速的开发JS应用,并在一定程度上改变了我们写JavaScript代码的习惯. 废话少说,直接进入正题,我 ...
- ARM内核全解析,从ARM7,ARM9到Cortex-A7,A8,A9,A12,A15到Cortex-A53,A57
转自: ARM内核全解析,从ARM7,ARM9到Cortex-A7,A8,A9,A12,A15到Cortex-A53,A57 前不久ARM正式宣布推出新款ARMv8架构的Cortex-A50处理器系列 ...
随机推荐
- 【原创】navicat for sqlite 11.1.12 patch 永久试用 不报毒
因为最近需要用这个但是网上都是注册机没有成功注册,所以就自己动手使用ollydbg开刀, 修改成了永久试用版本. 着急用所以没仔细分析,暂时先这样吧. 这个下载版本 http://dlsw.ba ...
- Redis数据结构之skiplist(续)
本文摘抄于<Redis内部数据结构详解-skiplist> 一.skiplist的由来 skiplist,顾名思义,首先它是一个list.实际上,它是在有序链表的基础上发展起来的. 我们先 ...
- js浮点数的加减乘除
;(function(root, factory) { // Support AMD if (typeof define === 'function' && define.amd) { ...
- Solr的配置和在java中的使用
Solr是一个全局站内搜索引擎,可以快速的搜索出结果. Solr依赖于tomcat,把Solr的war包放到tomcat中即可运行. 使用solr,需要在solr的schema.xml中配置solr与 ...
- SpringBoot使用Sharding-JDBC读写分离
本文介绍SpringBoot使用当当Sharding-JDBC进行读写分离. 1.有关Sharding-JDBC 本文还是基于当当网Sharding-Jdbc的依赖,与上一篇使用Sharding-Jd ...
- 手机QQ公众号亿级消息实时群发架构
编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由孙子荀分享.转载请注明来自高可用架构公众号 ArchNotes. 孙子荀,2009 年在华为从事内核和分布式系统的开发工作:2011 ...
- Android应用程序支持不同屏幕(尺寸、密度)
how to build a user interface using Android layouts for all types of devices 使用Android布局设计的UI接口用于不同的 ...
- USACO 邮票 Stamps
f[x]表示组成 x 最少需要的邮票数量 一一举例 最多贴5张邮票,有三种邮票可用,分别是1分,3分,8分 组成0分需要0张邮票 ——f[0]=0 组成1分需要在0分的基础上加上一张1分邮票 ——f[ ...
- 关于Promise层层嵌套可读性差问题
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大.它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象 ES6 规 ...
- 大数据计算框架Hadoop, Spark和MPI
转自:https://www.cnblogs.com/reed/p/7730338.html 今天做题,其中一道是 请简要描述一下Hadoop, Spark, MPI三种计算框架的特点以及分别适用于什 ...