終極真相:Go 中的參數(shù)傳遞
在 Go 社區(qū)常能聽到“按值傳遞”“按引用傳遞”兩種說法:
- 基本類型、數(shù)組、結(jié)構(gòu)體被稱為“按值”;
- 指針、切片、映射、通道則被稱為“按引用”。
然而,上述分類容易造成誤解。在 Go 語言中,一切函數(shù)實參都以 值 的形式被復(fù)制傳遞。區(qū)別僅在于:
- 復(fù)制的是“完整數(shù)據(jù)”(整數(shù)、數(shù)組等);
- 復(fù)制的是“描述符”或指針(切片、映射、字符串等)。
理解這一點(diǎn)后,再看各類示例便能水落石出。

純值類型:數(shù)組、結(jié)構(gòu)體等
func do(b [3]int) int {
b[0] = 0 // 只修改副本
return b[1]
}
func main() {
a := [3]int{1, 2, 3}
v := do(a) // 傳參時完整復(fù)制
fmt.Println(a, v) // [1 2 3] 2
}- a 在調(diào)用處被整體復(fù)制后傳入 do;
- do 內(nèi)部的任何修改都作用于該副本,原始數(shù)組保持不變。
- 若想在被調(diào)函數(shù)內(nèi)部改動調(diào)用者的數(shù)組,需改為 *[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) // 僅復(fù)制 map 頭部(指針)
fmt.Println(m) // map[3:1 4:3]
}- 傳入的 是一個指向運(yùn)行時哈希表的指針副本;
- 副本與原指針指向同一底層數(shù)據(jù),因此鍵值修改對調(diào)用者可見;
- 若在函數(shù)內(nèi)執(zhí)行 m = make(map[int]int),只會修改本地副本,不影響調(diào)用者。
切片:三字節(jié)描述符的特殊性
func do(s []int) int {
s = append(s, 4) // 可能觸發(fā)重新分配
s[0] = 0
return s[1]
}
func main() {
a := []int{1, 2, 3} // len=3 cap=3
v := do(a) // 復(fù)制切片頭部
fmt.Println(a, v) // [1 2 3] 2
}- 切片頭部 = 指針 + 長度 + 容量。
- append 時容量不足會 分配新數(shù)組 并返回新的切片頭部;
- 該新頭部僅存在于 do 中,調(diào)用者仍指向舊數(shù)組,因此 a[0] 未被修改。
若確需影響調(diào)用方切片的長度/容量,可顯式傳入 *[]T:
func do(s *[]int) {
*s = append(*s, 4) // 直接改寫調(diào)用者變量
(*s)[0] = 0
}參數(shù)永遠(yuǎn)“不是別名”
Go 永遠(yuǎn)復(fù)制實參,將其存入被調(diào)函數(shù)棧幀;函數(shù)內(nèi)變量 絕不是調(diào)用方變量的別名。想要修改調(diào)用者的數(shù)據(jù):就傳遞能“間接定位”到它的東西(指針或接口值的內(nèi)部指針)。
類型 | 傳遞時被復(fù)制的內(nèi)容 | 何時能改到調(diào)用方數(shù)據(jù) | 何時改不到 |
基本類型 | 整個值 | 無 | 總改不到 |
數(shù)組 | 整個數(shù)組 | 無 | 總改不到 |
結(jié)構(gòu)體 | 整個結(jié)構(gòu)體 | 無 | 總改不到 |
指針 | 指針本身 | 解引用后可改 | 修改指針本身 |
切片 | 指針+len+cap 描述符 | 修改底層數(shù)組元素 | 重新分配、改頭部 |
映射 | 指向哈希表的指針 | 改鍵值對 | 重新 make |
通道 | 指向 channel 結(jié)構(gòu)的指針 | 發(fā)/收消息 | 重新 make |
字符串 | 指針+len 描述符(數(shù)據(jù)只讀共享) | 無(只讀) | 任何寫操作 |
結(jié)論與建議
(1) 牢記:Go 只有按值傳遞。不要再談“按引用”,最多是“值里裝著指針”。
(2) 修改調(diào)用者數(shù)據(jù)的途徑只有兩種:
- 傳遞指針(*T、**T……);
- 傳遞內(nèi)部含指針的描述符(切片、map、通道)并操作其指向的共享數(shù)據(jù)。
(3) 重新分配(append、make 等)僅改變局部副本頭部,不會回寫調(diào)用方。
(4) 若你需要讓函數(shù)“生長”切片或重新綁定 map,使用指針語義:func f(s *[]T) 或 func g(m *map[K]V)。
正確理解參數(shù)傳遞語義,可避免誤判內(nèi)存行為、消除隱蔽 bug,使代碼行為更加可預(yù)測。





























