从一道题来看看golang中的slice作为参数时的现象

1、题目

最近看群友在群里问一道关于golang中slice的题,题目如下:

go 复制代码
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))
}

执行结果:

sh 复制代码
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)

go 复制代码
type slice struct {
	array unsafe.Pointer  // 数组指针
	len   int  // 长度
	cap   int  // 容量
}

slice 作为参数传递的时候,是将slice struct中的各个字段逐一复制到新的变量中去的,其中 array 字段是底层数组的首地址

我们一起来看看题目中变量K的初始化

go 复制代码
k := []int{1, 2, 3, 4}
k = append(k, 5, 6)

变量 K 示意图:

执行 ap 函数后

go 复制代码
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验证下 ```go 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) } 执行结果: ```sh 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返回出去就可以了。类似这样: ```go func ap(k []int) []int { k = append(k, 7, 8) return k } k = ap(k) ``` > 是不是有点像 append 内置函数

相关推荐
GO兔1 小时前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go
代码讲故事8 小时前
多种方法实现golang中实现对http的响应内容生成图片
开发语言·chrome·http·golang·图片·快照·截图
weixin_437398219 小时前
转Go学习笔记
linux·服务器·开发语言·后端·架构·golang
Code季风12 小时前
深入比较 Gin 与 Beego:Go Web 框架的两大选择
开发语言·golang·go·gin·beego
Code季风12 小时前
Gin 中间件详解与实践
学习·中间件·golang·go·gin
九班长12 小时前
Golang服务端处理Unity 3D游戏地图与碰撞的详细实现
3d·unity·golang
cui_win1 天前
【基础】Golang语言开发环境搭建(Linux主机)
linux·golang·运维开发
叹一曲当时只道是寻常1 天前
Softhub软件下载站实战开发(十):实现图片视频上传下载接口
golang·go·音视频
qq_168278951 天前
Protobuf在游戏开发中的应用:TypeScript + Golang 实践
服务器·golang·游戏引擎
大模型铲屎官11 天前
【Go语言-Day 7】循环控制全解析:从 for 基础到 for-range 遍历与高级控制
开发语言·人工智能·后端·golang·大模型·go语言·循环控制