go中传指针和传值本质区别在于:传值复制整个值,函数内修改不影响原变量;传指针复制地址,可修改调用方变量所指向的内存。map、slice等是含指针的值类型,能改底层数据但无法改变其header(如扩容不影响原slice)。

Go 里传指针和传值到底差在哪
Go 函数默认按值传递,哪怕你传的是一个结构体,也会复制整个值。想让函数修改原始变量,必须显式传指针。这不是“可选优化”,而是唯一能改调用方变量的途径。
常见误解是以为 map、slice、chan 传进去就能改原值——它们底层确实含指针,但它们本身仍是值类型:传的是 header 的副本。所以你能改底层数组内容(比如 slice[i] = x),但无法通过赋值改变调用方的 slice header(比如在函数里做 s = append(s, 1) 后,原 slice 不会变长)。
什么时候必须传 *T 而不是 T
以下情况不传指针就做不到目标:
- 需要在函数内修改调用方的变量值(如交换两个 int、更新 struct 字段)
- 结构体很大(比如含大数组或大量字段),避免拷贝开销
- 统一接口设计,比如标准库中
json.Unmarshal第二个参数必须是*interface{},因为要往里写解析结果
注意:小结构体(如 type Point struct{ X, Y int })传值反而更快,现代编译器可能直接寄存器传参,没必要盲目加 *。
func f(p *int) 怎么调用才不 panic
传 nil 指针进函数再解引用,就是 runtime panic:invalid memory address or nil pointer dereference。这比 Python 的 AttributeError 更沉默——编译器不报错,运行时才崩。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
安全做法:
- 调用前检查是否为 nil:
if p != nil { *p = 42 } - 文档或注释明确标注参数是否允许 nil(Go 没非空类型,全靠约定)
- 如果逻辑上不允许 nil,就在函数开头加
if p == nil { panic("p must not be nil") },比后面崩溃更早暴露问题
示例:
func increment(p *int) {
if p == nil {
return // 或 panic,看业务容忍度
}
*p++
}
接收者用指针还是值,和参数传指针是一回事吗
是同一套规则。方法接收者 func (t T) M() 是值接收者,func (t *T) M() 是指针接收者。区别完全对应函数参数:
- 想在方法里修改 t 的字段?必须用
*T - T 很大?用
*T避免拷贝 - 类型实现了某个 interface,那所有方法的接收者形式必须一致:不能一部分用
T,一部分用*T,否则只有部分方法满足 interface 约束
典型坑:定义了 func (t *MyStruct) Save(),却用 var x MyStruct; x.Save() —— 编译器会自动取地址,没问题;但若定义的是 func (t MyStruct) Save(),再用 var x *MyStruct; x.Save(),也会自动解引用,也没问题。真正出问题的是 interface 赋值时的隐式转换限制。
最常被忽略的一点:nil 指针可以合法调用指针方法(只要方法内部不解引用它),但值方法永远要求接收者非 nil(因为值本身就得存在)。这点在实现 interface 时容易踩空。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










