优先用值传递:小类型(int/string/小struct/固定数组)开销低、语义清、防误改、并发安全;必须用指针:需修改原值、值过大、含不可拷贝字段、指针接收者接口要求。

什么时候该用值传递,而不是指针传递
Go 里函数参数默认是值传递,这意味着传入的是原值的一份拷贝。对 int、string、struct{}(小结构体)、[3]int 这类小的值类型,值传递开销低、语义清晰、无副作用,优先选它。
常见误判点:以为 string 是引用类型所以必须传指针——其实 string 是只读的值类型,底层是 struct{data *byte, len int},本身仅 16 字节,传值非常轻量。
- 小结构体(字段总大小 ≤ 几个机器字长,比如 ≤ 32 字节)直接传值更高效,避免指针解引用和逃逸分析带来的堆分配
- 函数逻辑本就不该修改原始数据时,值传递天然防误改,比加注释或靠约定更可靠
- 并发场景下,传值能天然避免多个 goroutine 争用同一块内存,省去锁或 channel 协调成本
哪些情况必须或强烈建议用指针传递
核心判断标准:是否需要在函数内修改调用方的原始变量,或者值过大导致拷贝代价不可接受。
典型必须用指针的场景:
立即学习“go语言免费学习笔记(深入)”;
- 要修改原变量内容,比如
func reset(v *int) { *v = 0 };传值则修改无效 - 结构体字段多、含大数组或切片字段(如
type BigData struct { items [10000]int }),传值会触发大量内存拷贝,性能陡降 - 结构体中包含
sync.Mutex或其他不支持拷贝的字段(Go 编译器会报错:cannot assign to struct containing sync.Mutex) - 作为接口实现时,如果方法集包含指针接收者,那只有指针才能满足该接口(例如
func (p *MyType) Write(),则*MyType满足io.Writer,而MyType不满足)
struct 值传递 vs 指针传递的实际性能差异
别凭感觉猜,用 go test -bench 验证。一个含 4 个 int64 字段的结构体(32 字节),在现代 64 位机器上,值传递和指针传递的性能差距通常可忽略;但一旦字段扩展到含 [1024]byte,值传递的基准测试耗时会飙升数倍。
关键点:
- 逃逸分析(
go build -gcflags="-m")会告诉你变量是否被分配到堆上——值传递的小 struct 通常留在栈上;大 struct 或指针传递则更容易逃逸 - 使用
unsafe.Sizeof(T{})查看实际大小,比“看起来大”更可靠。例如time.Time是 24 字节,传值没问题;http.Request则内部含大量指针和大字段,必须传指针 - 切片、map、channel、func、interface 本身已是引用语义(底层含指针),传值开销固定且很小,无需额外取地址
容易被忽略的隐式指针行为
有些类型看着像值类型,实则是“伪值”——它们的底层包含指针,传值时只拷贝指针,不拷贝底层数组或哈希表。这类类型包括 []int、map[string]int、chan struct{}、func()、interface{}。
后果是:对这些类型做值传递,仍可能意外影响原值。例如:
func mutate(s []int) {
s[0] = 999 // 修改底层数组,调用方的 slice 也会看到
}
这不是 bug,是设计使然。但意味着:不要因为用了 slice 就以为“传值=安全”,需明确区分「修改 slice header」(如 s = append(s, x))和「修改底层数组」(如 s[i] = x)——后者会影响原 slice。
真正需要隔离修改时,应显式复制底层数组:copy(dst, src) 或 append([]T(nil), src...)。










