Go 语言中,函数参数传递始终遵循值传递规则,这一点没有例外。所谓“通过指针可以修改原值”,其本质在于传递的是地址这个值,通过解引用操作同一块内存——地址本身也是一个值,但它指向了原始变量所在的内存位置。

func f(x T) 中的 x 永远是副本
无论 T 是 int、string 还是 struct{} 类型,x 都是调用时传入值的一份副本。修改 x 不会影响调用方的原始变量。这个道理听起来简单,但在实际编码中经常容易踩坑。
- 传
int:副本里存储的是数字(比如42),修改它只是在修改副本,原始变量纹丝不动。 - 传
*int:副本里存储的是地址(比如0xc00001a080),通过*x = 100写入的是该地址指向的内存——这才是“修改原值”的真相。 - 传
[]byte或map[string]int:它们底层是包含指针的结构体,因此副本仍然指向同一底层数组或哈希表。但这仍然是值传递,只是“值”中包含了指针,切勿误认为是引用传递。
何时必须使用 *T 而非 T
不是“想改就用指针”,而是不传指针就无法达成目标时才必须使用。判断标准其实很清晰:
- 需要修改调用方的原始变量:比如
swap(a, b *int)或json.Unmarshal(data []byte, v interface{})要求v是指针。 - 结构体很大(字段多、包含大 slice/map、或数组长度大):避免复制开销。例如
type Config struct { Data [1e6]byte },传递Config会拷贝 1MB,性能堪忧。 - 方法接收者需要写字段:如
func (u *User) SetName(n string) { u.Name = n },值接收者(u User)修改了也无效,因为副本不会影响原变量。
常见 panic 及误判场景
向函数传递 nil 指针后再解引用,会直接触发 panic: runtime error: invalid memory address or nil pointer dereference。这是新手最容易犯的错误之一。
- 传递
nil *User然后执行u.Name = "x"→ panic。 - 但传递
User{}(零值)是安全的,即使字段是空 slice 或 map,也不会触发 panic。 - 不要认为“
slice是引用类型就能修改原 slice”:对s执行s = append(s, x)不会影响调用方,因为append可能分配新的底层数组,只修改了副本中的ptr。这是 slice 的经典陷阱。
真正容易被忽略的是:切片、map、channel 的“可变性”来源于其内部指针字段,而非语言层面的引用传递;而 *[]T、*map[K]V 这类指针类型本身可比较,但 []T 和 map[K]V 不可比较——这一点在作为 map key 或进行单元测试断言时经常踩坑,值得多加留意。
