从一道题来看看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作为参数时的现象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS判断点是否在线段上
- 下一篇: Kubernetes 漫游:理解 Con