Golang 切片作为函数参数传递的陷阱与解答

news/2024/11/18 4:46:49/文章来源:https://www.cnblogs.com/linguanh/p/18292709

作者:林冠宏 / 指尖下的幽灵。转载者,请: 务必标明出处。

GitHub : https://github.com/af913337456/

出版的书籍:

  • 《1.0-区块链DApp开发实战》
  • 《2.0-区块链DApp开发:基于公链》

  • 例子
    • 切片作为函数参数传递的是值
    • 用来误导切片作为函数参数传递的是引用
    • 函数内切片 append 引起扩容的修改将无效
    • 不引起切片底层数组扩容,验证没指向新数组
    • 脚踏实地让切片在函数内的修改
  • 彩蛋

切片 slice 几乎是每个 Go 开发者以及项目中 100% 会高频使用到的,Go 语言的知识很广,唯独 slice 我个人认为是必须要深入了解的。

乃至于今,网上还有很多关于切片 slice 技术文章一直存在的错误内容:切片作为函数参数传递的是引用,这是错误的。

无论是官方说明还是实践操作都表明:切片作为函数参数传递的是值,和数组一样。


接下来我们直接看例子以加深印象。

切片作为函数参数传递的是值的例子:
func main() {mSlice := []int{1, 2, 3}fmt.Printf("main-1: %p \n", &mSlice) // 0x140000b2000mAppend(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   intcap   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 > lenfmt.Printf("main-1: %v, 数组地址: %p \n", mSlice, mSlice) // [0,0,0], 0x14000120000mAppend(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], 0x1400001a0a8mAppend(&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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/741321.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

暑假读论文总结

7.8SAM-G 待填7.9MAE(Masked Autoencoders Are Scalable Vision Learners) 来源:CVPR 2022 在视觉领域应用 auto encoder 的比较早的工作了,是自监督学习。 主要内容是在原图中选择若干个 patch 进行遮挡(patch 通常选的很多,~75%),通过 encoder - decoder 进行复原。e…

设计模式学习(二)工厂模式——抽象工厂模式+注册表

介绍抽象工厂模式初版代码的改进方案目录前言使用简单工厂改进使用注册表改进参考文章 前言 在上一篇文章中我们提到了抽象工厂模式初版代码的一些缺点:①客户端违反开闭原则②提供方违反开闭原则。本文将针对这两点进行讨论 使用简单工厂改进 对于缺点①,我们可以使用简单工…

服务器怎么连接?服务器远程连接图文教程

服务器操作系统可以实现对计算机硬件与软件的直接控制和管理协调,任何计算机的运行离不开操作系统,服务器也一样,服务器操作系统主要分为四大流派:Windows Server、Netware、Unix和Linux 今天飞飞就给你们分享下常用的Windows、Linux、Unix三种系统的远程连接图文操作方法服…

Candy Party (Hard Version)

这个就看官方题解就好了,写的很清楚 考试的时候把easy version给做出来了,但是对于hard version确实没有想到可以转换成位运算 所以以后看到\(2^x\),不妨想一下是不是位运算,这里将最后的式子一列就知道是位运算了

【Azure App Service】访问App Service应用报错 SSL: WRONG_VERSION_NUMBER

REST API: write EPROTO 8936192:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:..\..\third_party\boringssl\src\ssl\tls_record.cc:231. Python: urllib3.exceptions.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1000)问题…

题解 - 修剪草坪

单调队列优化 dp题目(in 洛谷) 或 题目(in hszxoj) 题目大意给定 \(n\) 个非负整数 \(a_1 \cdots a_n\)。现在你可以选择其中若干个数,但不能有超过 \(k\) 个连续的数字被选择。 求选出的数字的和最大。思路简析 一个比较好的思路是反向思考:选择某些间隔小于等于 \(k\) …

函数进阶应用2

进阶函数的具体应用场景查询并标记停产商品要求:停产商品标记为红颜色具体操作 选中B列——开始选项卡——条件规则——新建规则,选择“使用公式……格式的单元格”——在公式输入框中输入“=VLOOKUP(B2,选择区域,返回列,精确匹配)="停产”——格式,选填充色为红色—…

04 安装SSH

因为每一个老嵌入式都喜欢使用他的老windows进行开发,因此我决定使用SSH来开发rust,这样也不用在虚拟机里边再装一个vscode. 参考博客如何在windows下使用vscode连接linux虚拟机进行代码开发_windows vscode编辑linux文件-CSDN博客 Windows环境使用VSCode 调试Linux环境C/C++代…

Nuxt框架中内置组件详解及使用指南(四)

摘要:本文详细介绍了Nuxt 3框架中的两个内置组件:title: Nuxt框架中内置组件详解及使用指南(四) date: 2024/7/9 updated: 2024/7/9 author: cmdragon excerpt: 摘要:本文详细介绍了Nuxt 3框架中的两个内置组件:和的使用方法与示例。用于捕获并处理客户端错误,提供了错…

奇异值分解以及matlab实现

奇异值分解(Singular Value Decomposition)是线性代数中一种重要的矩阵分解,具有压缩矩阵信息的作用 目录一、奇异值分解的理论介绍1.奇异值分解的例子2.U的计算3.V的计算4.Σ的计算5.利用SVD对数据进行"降维"(1)对U与V进行分块,得到分块矩阵(2)去除奇异值后…

php:安装phpredisadmin

一,项目代码地址: https://github.com/erikdubbelboer/phpRedisAdmin 二,下载: 从命令行用wget下载 [root@blog phpredisadmin]# wget https://github.com/erikdubbelboer/phpRedisAdmin/archive/refs/tags/v1.20.0.tar.gz 下载完成后解压缩 [root@blog phpredisadmin]# tar …

数学推导

基本公式 (a+b)%mod=(a%mod+b%mod)%mod 设一个任意整数\(A=a*10^n+b*10^{n-1}+...+c\). 由此可以证明 \(A \quad mod \quad m=(((a \quad mod \quad m)*10+b \quad mod \quad m)*10...)+c) \quad mod \quad m\) 该证明可以应用在数位DP点击查看代码 #include<bits/stdc++.h…