如何理解 Golang 的參數傳遞都是值傳遞?
在 Golang 中函數之間傳遞變量時總是以值的方式傳遞的,無論是 int,string,bool,array 這樣的內置類型(或者說原始的類型),還是 slice,channel,map 這樣的引用類型,在函數間傳遞變量時,都是以值的方式傳遞,也就是說傳遞的都是值的副本。
內置類型參數傳遞
內置類型傳遞的時候是值的副本,這個好理解,隨便舉個例子:
- package main
- import (
- "fmt"
- )
- func main() {
- num := 10
- num2 := increase(num, 10)
- fmt.Println(num2)
- }
- func increase(num int, add int) int {
- return num + add
- }
這里 num 傳入 increase 函數,是拷貝值的副本,并且返回一個新的值。假設 num 是一個很大的數組,那么傳遞給函數的就是這個很大數組的拷貝。(這樣很浪費內存,真實情況如果要傳一個很大的數組,應該傳遞數組的指針)
引用類型的參數傳遞
引用類型的參數傳遞也是值的拷貝。
例子:
- package main
- import (
- "fmt"
- )
- func main() {
- slice1 := []string{"zhang", "san"}
- modify(slice1)
- fmt.Println(slice1)
- }
- func modify(data []string) {
- data = nil
- }
運行結果:
[zhang san]
這個例子證明了作為引用類型的切片,參數傳遞不是傳的引用,而是傳的值,如果是傳的引用,那么函數對它的修改會受到影響,而這里切片內容并沒有改變成 nil.
但是有一個例子比較誤導人,我們看一看:
- package main
- import (
- "fmt"
- )
- func main() {
- slice1 := []string{"zhang", "san"}
- modify(slice1)
- fmt.Println(slice1)
- }
- func modify(data []string) {
- data[1] = "si"
- }
運行結果:
[zhang si]
這里為什么改變了切片的內容呢?
什么是標頭?
搞清楚這個問題,首先要知道什么是“標頭”這個概念?引用《Go語言實踐》中的一段話:
Go 語言里的引用類型有如下幾個:切片、映射、通道、接口和函數類型。當聲明上述類型的變量時,創建的變量被稱作標頭(header)值。從技術細節上說,字符串也是一種引用類型。每個引用類型創建的標頭值是包含一個指向底層數據結構的指針。因為標頭值是為復制而設計的,所以永遠不需要共享一個引用類型的值。標頭值里包含一個指針,因此通過復制來傳遞一個引用類型的值的副本,本質上就是在共享底層數據結構。
總而言之,引用類型在函數傳遞的時候,是值傳遞,只不過這里的“值”指的是標頭值。
我們分別打印這個切片變量傳參前后的指針地址,和傳參前后切片中元素的指針地址:
- package main
- import (
- "fmt"
- )
- func main() {
- slice1 := []string{"zhang", "san"}
- fmt.Printf("%p\n", &slice1)
- fmt.Printf("%p\n", &slice1[1])
- modify(slice1)
- fmt.Println(slice1)
- }
- func modify(data []string) {
- fmt.Printf("%p\n", &data)
- fmt.Printf("%p\n", &data[1])
- data[1] = "si"
- }
運行結果:
0xc42000a060
0xc42000a090
0xc42000a0a0
0xc42000a090
這再次證明了切片傳遞的不是指針地址,因為變量前后地址不同。
這也證明了切片的參數傳遞的是傳值的形式,具體是傳標頭值的拷貝,因為指向元素的指針地址相同。