从一道题来看看golang中的slice作为参数时的现象
1、题目
最近看群友在群里问一道关于golang中slice的题,题目如下:
package main
import "fmt"
func main() {
k := []int{1, 2, 3, 4}
k = append(k, 5, 6)
fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))
ap(k)
fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))
}
func ap(k []int) {
k = append(k, 7, 8)
fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))
}
执行结果:
k --> value: [1 2 3 4 5 6], add: 0xc00001e180, cap: 8
k --> value: [1 2 3 4 5 6 7 8], add: 0xc00001e180, cap: 8
k --> value: [1 2 3 4 5 6], add: 0xc00001e180, cap: 8
乍一看,还挺奇怪的,变量k的地址都是一样的,为啥会执行ap函数时,打印出来的东西不一样呢?
其实对于初次接触 golang 的 gopher 而言,这个问题确实有点奇怪,书上不是说slice是引用类型,golang 中的函数传参是值拷贝,那么在函数传递 slice 时,传递也是地址,为啥对地址指向的内容做了修改后,并没有影响到其他指向同一地址的变量呢?
想要理解这里面的原理,需要了解下面的基础知识,接下来我们先看看前置知识,学习完这些前置的理论后,相信大家都已经有了自己的理解与答案。
PS: 要是有理解不对的地方,请不吝赐教哈,谢谢。
2、前置理论
2.1、切片的本质
下面的介绍基于 go 1.18,golang中关于 slice 封装的源码位于
runtime/slice.go中。
切片的本质就是对底层数组的封装,切片实际上是一个 struct ,包含了三个字段:底层数组的指针、切片的长度(len)和切片的容量(cap)。
type slice struct {
array unsafe.Pointer // 数组指针
len int // 长度
cap int // 容量
}
slice 作为参数传递的时候,是将slice struct中的各个字段逐一复制到新的变量中去的,其中 array 字段是底层数组的首地址。
我们一起来看看题目中变量K的初始化
k := []int{1, 2, 3, 4}
k = append(k, 5, 6)
变量 K 示意图:

执行 ap 函数后
func ap(k []int) {
k = append(k, 7, 8) // 无需扩容,容量足够
fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))
}
函数内变量k的示意图:

2.2、格式化字符串%p打印slice时显示的是什么
这个问题呢,推荐大家看下这篇文章,比我说得清楚写。
[golang slice切片到底是指针吗?为什么%p输出的切片是地址?](https://segmentfault.com/a/1190000042430248)
这里我们写一个demo验证下
func main() {
k := []int{1, 2, 3, 4}
fmt.Printf("k --> add: %p\n", k)
fmt.Printf("k[0] --> add: %p\n", &k[0])
}
执行结果:
k --> add: 0xc000136000
k[0] --> add: 0xc000136000
3、再看题目
了解了上面的知识后,再看开头的题目就很简单了,变量k 传给 ap 函数函数时,虽然函数 ap 的形参也叫 k,但是已经不是同一个变量了,只是两个 slice 指向的底层数组是同一个而已,所以使用 %p 打印时,显示的地址是一样的。
package main
import "fmt"
func main() {
k := []int{1, 2, 3, 4}
k = append(k, 5, 6)
fmt.Printf("k --> value: %v, add: %p, len: %d, cap: %d\n", k, k, len(k), cap(k))
fmt.Printf("k --> add: %p\n", &k)
ap(k)
fmt.Printf("k --> value: %v, add: %p, len: %d, cap: %d\n", k, k, len(k), cap(k))
fmt.Printf("k --> add: %p\n", &k)
}
func ap(k []int) {
k = append(k, 7, 8)
fmt.Printf("k --> value: %v, add: %p, len: %d, cap: %d\n", k, k, len(k), cap(k))
fmt.Printf("k --> add: %p\n", &k)
}
执行结果:
k --> value: [1 2 3 4 5 6], add: 0xc00001e180, len: 6, cap: 8
k --> add: 0xc00000c030
k --> value: [1 2 3 4 5 6 7 8], add: 0xc00001e180, len: 8, cap: 8
k --> add: 0xc00000c078
k --> value: [1 2 3 4 5 6], add: 0xc00001e180, len: 6, cap: 8
k --> add: 0xc00000c030
想要 ap 函数执行后的结果,能够改变外面的变量k也很简单,将函数中的形参k返回出去就可以了。类似这样:
func ap(k []int) []int {
k = append(k, 7, 8)
return k
}
k = ap(k)
是不是有点像 append 内置函数
从一道题来看看golang中的slice作为参数时的现象的更多相关文章
- golang中,slice的几个易混淆点
slice在golang中是最常用的类型,一般可以把它作为数组使用,但是比数组要高效呀.不过,我感觉这个东西用的不好坑太多了.还是需要了解下他底层的实现 slice的结构定义 type slice s ...
- Golang中设置函数默认参数的优雅实现
在Golang中,我们经常碰到要设置一个函数的默认值,或者说我定义了参数值,但是又不想传递值,这个在python或php一类的语言中很好实现,但Golang中好像这种方法又不行.今天在看Grpc源码时 ...
- Golang中的Slice与数组
1.Golang中的数组 数组是一种具有固定长度的基本数据结构,在golang中与C语言一样数组一旦创建了它的长度就不允许改变,数组的空余位置用0填补,不允许数组越界. 数组的一些基本操作: 1.创建 ...
- 【Java学习笔记之二十七】Java8中传多个参数时的方法
java中传参数时,在类型后面跟"..."的使用: public static void main(String[] args){ testStringA ...
- golang中的slice翻转存在以及map中的key判断
//slice翻转 func stringReverse(src []string){ if src == nil { panic(fmt.Errorf("the src can't be ...
- Go_17:GoLang中如何使用多参数属性传参
我们常常因为传入的参数不确定而头疼不已,golang 为我们提供了接入多值参数用于解决这个问题.但是一般我们直接写已知代码即所有的值都知道一个一个塞进去就好了,但是绝大部分我们是得到用户的大量输入想通 ...
- GoLang中如何使用多参数属性传参
我们常常因为传入的参数不确定而头疼不已,golang 为我们提供了接入多值参数用于解决这个问题.但是一般我们直接写已知代码即所有的值都知道一个一个塞进去就好了,但是绝大部分我们是得到用户的大量输入想通 ...
- golang中函数的可变参数
package main import "fmt" // 一个函数中最多只可有一个可变参数, 如果参数列表中还有其它类型的参数,则可变参数写在最后 // 注意:参数不定,参数的个数 ...
- golang中往脚本传递参数的两种用法os.Args和flag
1. os.Args package main import ( "fmt" "os" ) func main() { // 执行:./demo.exe 127 ...
- OC中block作方法参数时的用法
方式一.在传参时直接声明block回调方法. 1. 定义方法: - (int)doTest:(NSString *)name para1:(int)temp1 para2:(int)temp2 suc ...
随机推荐
- ls 和 du显示文件大小不一样
查看当前文件系统的磁盘使用 df -k / Filesystem 1K-blocks Used Available Use% Mounted on /dev/nvme0n1p2 97844508 37 ...
- 西门子HMI切换页面时的指示功能
怎么样才能做到像这样按下切换界面的按钮,切换过去之后对应的切换按钮还进行高亮指示呢? 首先我们要先新建模板,把我们的画面拖拽到模板里就会形成按钮 在画面的"属性"中 在属性中选上我 ...
- quarkus依赖注入之六:发布和消费事件
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus依赖注入> ...
- 基于inotify-tools和rsync实现文件实时同步
前言 系统:centos 7 inotify-tools版本:3.14(3.22版本测试有问题,所以找的比较旧的一版) 主机IP: 192.168.137.7(服务端,接收同步文件) 192.168. ...
- RR有幻读问题吗?MVCC能否解决幻读?
幻读是 MySQL 中一个非常普遍,且面试中经常被问到的问题,如果你还搞不懂什么是幻读?什么是 MVCC?以及 MySQL 中的锁?那么请好好收藏和阅读本篇文章,因为它非常重要. RR 隔离级别 在 ...
- QA|20211013|SecureCRT:如图,有很多^,中文显示有问题,乱码,如何解决
Q1:如图,有很多^,中文显示有问题,乱码,如何解决 Q2:securecrt的vi展示有问题:少很多字.有很多^M和^,光标无法移动到最右侧 A: 首先检查当前编码格式: 1 echo $LANG ...
- iFiles浏览iphone文件
我们希望能在iphone中浏览文件系统的目录
- Go学习笔记1
学习路线 2023-Go全链路工程师课纲 https://www.processon.com/view/link/63594cd97d9c0854f9ac855e 一.搭建环境 https://stu ...
- 虚拟机问题:VMware Workstation 与 Device/Credential Guard 不兼容。在禁用 Device/Credential Guard 后,可以运行
解决办法:关闭Hyper-V功能. windows10使用VMware Workstation打开虚拟机时显示 VMware Workstation 与 Hyper-V 不兼容.请先从系统中移除 Hy ...
- Vue源码学习(五):<templete>渲染第四步,生成虚拟dom并将其转换为真实dom
好家伙, 前情提要: 在上一篇我们已经成功将ast语法树转换为渲染函数 现在我们继续 1.项目目录 代码已开源https://github.com/Fattiger4399/analytic ...