Golang 切片作为函数参数传递的陷阱与解答
作者:林冠宏 / 指尖下的幽灵。转载者,请: 务必标明出处。
GitHub : https://github.com/af913337456/
出版的书籍:
- 例子
- 切片作为函数参数传递的是值
- 用来误导切片作为函数参数传递的是引用
- 函数内切片 append 引起扩容的修改将无效
- 不引起切片底层数组扩容,验证没指向新数组
- 脚踏实地让切片在函数内的修改
- 彩蛋
切片 slice 几乎是每个 Go 开发者以及项目中 100% 会高频使用到的,Go 语言的知识很广,唯独 slice 我个人认为是必须要深入了解的。
乃至于今,网上还有很多关于切片 slice 技术文章一直存在的错误内容:切片作为函数参数传递的是引用,这是错误的。
无论是官方说明还是实践操作都表明:切片作为函数参数传递的是值,和数组一样。
接下来我们直接看例子以加深印象。
切片作为函数参数传递的是值的例子:
func main() {
mSlice := []int{1, 2, 3}
fmt.Printf("main-1: %p \n", &mSlice) // 0x140000b2000
mAppend(mSlice)
fmt.Printf("main-2: %p \n", &mSlice) // 0x140000b2000
}
func mAppend(slice []int) {
fmt.Printf("append func: %p \n", &slice) // 0x140000b2018 和外部的不一样
}
错觉例子,也是现在用来误导切片作为函数参数传递的是引用的错误文章常用的:
func main() {
mSlice := []int{1, 2, 3}
fmt.Printf("main-1: %v \n", mSlice) // [1,2,3]
mAppend(mSlice)
fmt.Printf("main-2: %v \n", mSlice) // [1,9,3],这里2被修改了,但不是引用传递导致的
}
func mAppend(slice []int) {
slice[2] = 9 // 修改
}
切片的内部结构:
// 源码路径:go/src/runtime/slice.go
type slice struct {
array unsafe.Pointer // 指针
len int
cap int
}
切片的本质是 struct,作为函数参数传递时候遵循 struct 性质,array 是指针指向一个数组,len 是数组的元素个数,cap 是数组的的长度。当 len > cap,将触发数组扩容。
解析: 为什么上面的 错觉例子 能在函数内部改变值且在外部生效。这是因为当切片作为参数传递到函数里,虽然是值传递,但函数内拷贝出的新切片的 array 指针所指向的数组和外部的旧切片是一样的,那么在没引起扩容情况下进行值的修改就生效了。
旧切片 array 指针 ---> 数组-1
新切片 array 指针 ---> 数组-1,函数内发生改变
函数内切片 append 引起扩容的修改将无效的例子:
func main() {
mSlice := []int{1, 2, 3}
fmt.Printf("main-1: %v \n", mSlice) // [1,2,3]
mAppend(mSlice)
fmt.Printf("main-2: %v \n", mSlice) // [1,2,3] 没生效
}
func mAppend(slice []int) {
// slice[2] = 9 // 修改
slice = append(slice, 4)
fmt.Printf("append: %v \n", slice) // [1,2,3,4]
}
解析:切片初始化时候添加了3个数,导致其 len 和 cap 都是3,函数内添加第四个数的时候,触发扩容,而扩容会导致扩容,array 指针指向新的数组,在函数结束后,旧切片数组并没修改。
旧切片 array 指针 ---> 数组-1 值 [1,2,3]
新切片 array 指针 ---> 数组-2 值 [1,2,3,4]
不引起切片底层数组扩容,验证没指向新数组例子:
func main() {
mSlice := make([]int, 3, 4) // len = 3, cap = 4, cap > len
fmt.Printf("main-1: %v, 数组地址: %p \n", mSlice, mSlice) // [0,0,0], 0x14000120000
mAppend(mSlice)
fmt.Printf("main-2: %v, 数组地址: %p \n", mSlice, mSlice) // [0,0,0], 0x14000120000
}
func mAppend(slice []int) {
slice = append(slice, 4)
fmt.Printf("append: %v, 数组地址: %p \n", slice, slice) // [0,0,0,4], 0x14000120000
}
解析:可以看到切片的底层数组地址并没改变,但是数组的值依然没改变。这是因为切片是值传递到函数内部的,此时的 len 依然是值传递,当打印的时候,就只打印 len 以内的数据。
旧切片 len = 3
新切片 len = 4,函数内改变
至此,我们应该如何让切片在函数内的修改生效?答案就是规规矩矩使用指针传参。
func main() {
mSlice := []int{1, 2, 3}
fmt.Printf("main-1: %v, 数组地址: %p \n", mSlice, mSlice) // [1,2,3], 0x1400001a0a8
mAppend(&mSlice)
fmt.Printf("main-2: %v, 数组地址: %p \n", mSlice, mSlice) // [1,2,3,4], 0x1400001a0a8
}
func mAppend(slice *[]int) {
*slice = append(*slice, 4)
fmt.Printf("append: %v, 数组地址: %p \n", *slice, slice) // [1,2,3,4], 0x140000181b0
}
上面例子成功在函数内使用 append 修改了切片,也可以看到切片数组地址变了,这是因为引起了扩容。但 array 指针没变,所以扩容后,指向了新的。
旧切片 array 指针 ---> 数组-1 值 [1,2,3]
旧切片 array 指针 ---> 数组-2 值 [1,2,3,4]
彩蛋
切片的扩容:
go1.18之前,临界值为1024,len 小于1024时,切片先2倍 len 扩容。大于 1024,每次增加 25% 的容量,直到新容量大于期望容量;
go1.18之后,临界值为256,len 小于256,依然2倍 len 扩容。大于256走算法:
newcap += (newcap + 3*threshold) / 4,直到满足。(threshold = 256)
Golang 切片作为函数参数传递的陷阱与解答的更多相关文章
- golang 切片小记
1 切片初始化 func printSlice(s []int) { fmt.Printf("len=%d cap=%d underlying array:%p, %v\n", l ...
- Golang基础之函数
golang基础之函数 1.为什么需要函数? 有些相同的代码可能出现多次,如果不进行封装,那么多次写入到程序中,会造成程序冗余,并且可读性降低 2.什么是函数 为完成某些特定功能的程序指令集合称为函数 ...
- Golang面向过程编程-函数
Golang面向过程编程-函数 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是函数 简单的说函数的作用就是把程序里多次调用的相同的代码部分定义成一份,然后起个名字,所有的 ...
- shell 脚本之获取命令输出字符串以及函数参数传递
在ubuntu 14.04之后,所有的U盘挂载也分用户之分,最近很多操作也和U盘有关,所以就研究了一上午shell脚本函数以及字符串操作的方法. 字符串操作: 获取他的命令输出比较简单,打个简单的比方 ...
- Python语言特性之1:函数参数传递
问题:在Python文档中好像没有明确指出一个函数参数传递的是值传递还是引用传递.如下面的代码中"原始值"是不放生变化的: class PassByReference: def _ ...
- Go的50度灰:Golang新开发者要注意的陷阱和常见错误
Go的50度灰:Golang新开发者要注意的陷阱和常见错误 http://colobu.com/2015/09/07/gotchas-and-common-mistakes-in-go-golang/
- VB几种函数参数传递方法,Variant,数组,Optional,ParamArray
VB几种函数参数传递方法,Variant,数组,Optional,ParamArray 一) 过程的参数被缺省为具有 Variant 数据类型. 1)ByRef按 地址传递参数在 VB 中是缺省的 按 ...
- Python 函数参数传递机制.
learning python,5e中讲到.Python的函数参数传递机制是对象引用. Arguments are passed by assignment (object reference). I ...
- 【转】Python函数默认参数陷阱
[转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...
- Python面试题目之Python函数默认参数陷阱
请看如下一段程序: def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_l ...
随机推荐
- $KMP$学习记
<不浪漫罪名>--王杰 没有花 这刹那被破坏吗 无野火都会温暖吗 无烟花一起庆祝好吗 若爱恋 仿似戏剧那样假 如布景一切都美化 连相拥都参照主角吗 你说我未能定时 令你每天欢笑一次 我没说 ...
- Unity Linear Gamma色彩空间矫正测试
Gamma和Linear修正的问题相信网上已经有很多文章了.简单来说显示器的颜色输出不是线性的,根据硬件参数和输出颜色 信息拟合曲线是x^2.2,因此会使用一个x^0.45曲线将其拟合回线性. 因为0 ...
- PHP-FPM 运行原理分析
概述 PHP-FPM 是一种 Master / Worker 多进程运行模式,进程的数量可以通过 php-fpm.conf 进行具体的配置. Master 进程主要负责 CGI.PHP 环境的初始化. ...
- Vue cli之传递数据
1.父组件的数据传递给子组件 // 父组件 <Menu title="来自Home的数据" :clickNum="num"></Menu> ...
- 微服务新体验之Aspire初体验
安装aspire 查看vs版本 我这的版本是17.9.7,不支持aspire,所以需要升级 更新VS 点击 帮助->检查更新 点击更新 静等安装升级 创建aspire项目 项目创建成功,如下图 ...
- Keil_MDK中无法打开map文件的解决办法
如果在MDK中打开map文件 我们在STM32的开发过程中,经常会查看.map文件 .map文件是MDK在编译过程中生成的一个包含镜像文件信息的重要文件,在程序编译后自动生成,比方这里我的工程下自动将 ...
- Math Record
T1.P3327 知识点:莫比乌斯反演,数论分块 我们知道 \(d(ij) = \sum_{x | i}\sum_{y | j}[\gcd(x,y) == 1]\). 所以我们就要求 \(\sum^n ...
- C# .NET 压缩ZIP时 OOM OutOfMemoryException
C# .NET 压缩ZIP时 OOM OutOfMemoryException. ZipArchiveEntry.ZipEntry.SharpZipLib.ZipOutputStream.OutOfM ...
- 通过JS来触发<a>链接来实现图片下载
function downloadImg(){ var url = '实际情况的图片URL'; // 获取图片地址 var a = document.createElement('a'); // 创建 ...
- django通过channels实现websocket
WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符.它实现了浏览器与服务器全双工(full-duplex ...