終極真相:Go 中的參數傳遞
在 Go 社區常能聽到 “按值傳遞”“按引用傳遞” 兩種說法:
-
基本類型、數組、結構體被稱爲 “按值”;
-
指針、切片、映射、通道則被稱爲 “按引用”。
然而,上述分類容易造成誤解。在 Go 語言中,一切函數實參都以 值 的形式被複制傳遞。區別僅在於:
-
複製的是 “完整數據”(整數、數組等);
-
複製的是 “描述符” 或指針(切片、映射、字符串等)。
理解這一點後,再看各類示例便能水落石出。
純值類型:數組、結構體等
func do(b [3]int) int {
b[0] = 0 // 只修改副本
return b[1]
}
func main() {
a := [3]int{1, 2, 3}
v := do(a) // 傳參時完整複製
fmt.Println(a, v) // [1 2 3] 2
}
-
a在調用處被整體複製後傳入do; -
do內部的任何修改都作用於該副本,原始數組保持不變。 -
若想在被調函數內部改動調用者的數組,需改爲
*[3]int。
描述符類型:映射(map)
func do(m map[int]int) {
m[3] = 1
m[4] = 3
}
func main() {
m := map[int]int{4: 2}
do(m) // 僅複製 map 頭部(指針)
fmt.Println(m) // map[3:1 4:3]
}
-
傳入的 是一個指向運行時哈希表的指針副本;
-
副本與原指針指向同一底層數據,因此鍵值修改對調用者可見;
-
若在函數內執行
m = make(map[int]int),只會修改本地副本,不影響調用者。
切片:三字節描述符的特殊性
func do(s []int) int {
s = append(s, 4) // 可能觸發重新分配
s[0] = 0
return s[1]
}
func main() {
a := []int{1, 2, 3} // len=3 cap=3
v := do(a) // 複製切片頭部
fmt.Println(a, v) // [1 2 3] 2
}
-
切片頭部 = 指針 + 長度 + 容量。
-
append時容量不足會 分配新數組 並返回新的切片頭部; -
該新頭部僅存在於
do中,調用者仍指向舊數組,因此a[0]未被修改。
若確需影響調用方切片的長度 / 容量,可顯式傳入 *[]T:
func do(s *[]int) {
*s = append(*s, 4) // 直接改寫調用者變量
(*s)[0] = 0
}
參數永遠 “不是別名”
Go 永遠複製實參,將其存入被調函數棧幀;函數內變量 絕不是調用方變量的別名。
想要修改調用者的數據:就傳遞能 “間接定位” 到它的東西(指針或接口值的內部指針)。
結論與建議
-
牢記:Go 只有按值傳遞。不要再談 “按引用”,最多是 “值裏裝着指針”。
-
修改調用者數據的途徑只有兩種:
-
傳遞指針(
*T、**T……); -
傳遞內部含指針的描述符(切片、map、通道)並操作其指向的共享數據。
-
-
重新分配(
append、make等)僅改變局部副本頭部,不會回寫調用方。 -
若你需要讓函數 “生長” 切片或重新綁定 map,使用指針語義:
func f(s *[]T)或func g(m *map[K]V)。
正確理解參數傳遞語義,可避免誤判內存行爲、消除隱蔽 bug,使代碼行爲更加可預測。
參考
-
Go Class: 04 Strings[1]
-
Go Class: 08 Functions, Parameters & Defer[2]
-
Go Slices: usage and internals[3]
-
Demystifying function parameters in Go[4]
-
Go Data Structures[5]
參考資料
[1] Go Class: 04 Strings: https://www.youtube.com/watch?v=nxWqANttAdA&list=PLoILbKo9rG3skRCj37Kn5Zj803hhiuRK6&index=5
[2] Go Class: 08 Functions, Parameters & Defer: https://www.youtube.com/watch?v=wj0hUjRHkPs&list=PLoILbKo9rG3skRCj37Kn5Zj803hhiuRK6&index=9&t=9s
[3] Go Slices: usage and internals: https://go.dev/blog/slices-intro#:~:text=Go%E2%80%99s%20arrays%20are%20values,think%20about%20arrays%20is%20as
[4] Demystifying function parameters in Go: https://www.alexedwards.net/blog/demystifying-function-parameters-in-go
[5] Go Data Structures: https://research.swtch.com/godata#:~:text=A%20,a%20potentially%20different%20pointer%20and
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/IhZRi_1DvgRGnzs6Qv-18A