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)\](