Go只有值传递,传指针本质是传地址副本;能否修改原变量取决于是否解引用赋值及参数是否指向它,slice/map/chan因底层含指针而表现类似引用但header仍为值类型。

Go 里没有引用传递,只有值传递;所谓“用指针实现引用效果”,本质是传 *T 值(即地址的副本),再通过 *p = ... 修改原内存。能不能改外部变量,只取决于你有没有解引用赋值,以及传的是不是指向它的指针。
为什么传 *int 能改原变量,但传 int 不行
传 int 是复制一份值,函数内所有操作都发生在副本上;传 *int 是复制一个地址值,它仍指向原变量所在内存位置。
- 正确写法:
func increment(x *int) { *x++ }→ 调用increment(&a)后,a真的变了 - 错误写法:
func bad(x *int) { x = new(int); *x = 42 }→ 只改了指针副本的指向,&a没动,a还是原来值 - 常见误判:看到
func (u *User) SetName(...)就以为“方法接收者用指针=自动改原值”,其实前提是调用时u是可寻址的(比如变量、切片元素),不能是字面量或不可寻址表达式
slice、map、chan 为什么常被误认为“引用传递”
它们底层含指针,但 header(头信息)本身仍是值类型:传参时复制 header,底层数组/哈希表共享,但 header 字段(如 len、cap、ptr)是否被修改,决定了调用方能否感知变化。
-
slice:传值可改元素(s[0] = 1生效),但s = append(s, x)不生效——因为返回新 header,原变量未变;要就地更新必须传*[]T或显式返回 -
map和chan:增删查改都不改自身指针值,所以传值足够;func (m *StringMap) Set()多余,还误导人以为要改 map 变量本身 - 错误现象:
append后长度没变、delete(m, k)却生效 —— 差别就在“改的是底层数组/哈希表”还是“改的是 slice header / map 变量”
什么时候必须传 *T,而不是 T
不是“想改就加星号”,而是两个硬性条件:需要修改调用方变量本身的值,或类型太大不想拷贝。
立即学习“go语言免费学习笔记(深入)”;
- 改字段值:比如
type Config struct{ data map[string]string },工厂函数必须返回*Config并在内部做c.data = make(...),否则data字段为 nil - 避免大结构体拷贝:1MB 的 struct 传值开销明显,传
*T固定 8 字节(64位);sync.Mutex必须用指针——值拷贝会复制锁对象,导致死锁或竞态 - 接口一致性:只要该类型有一个方法用了指针接收者(如
Write),其他方法也建议统一用指针,否则可能无法满足接口实现 - 反例:对
int、string、小 struct 盲目传指针,既没收益,还增加nilpanic 风险
nil 指针解引用 panic 怎么防
直接 *p 前不检查,运行时就报 runtime error: invalid memory address or nil pointer dereference。这不是风格问题,是必踩的坑。
- 函数开头加
if p == nil { return }或if p == nil { return errors.New("p is nil") } - 方法接收者用指针时,Go 允许
var v T; v.Method()自动取地址,但如果v是零值且方法内解引用nil字段(比如v.cfg是nil *Config),照样 panic - 不要用
&"hello"或&42—— 字面量不可寻址,编译直接失败 - 最容易被忽略的是:结构体内嵌指针字段(如
type User { Profile *Profile }),误以为整个结构体必须用指针传;其实只要不改User.Profile这个字段的值(即让它指向别的Profile),传User值也行
判断要不要传指针,始终盯住一点:你操作的是 header 字段(ptr/len/cap)还是仅其指向的数据。slice 的行为边界最模糊——它既不像纯值,也不像纯引用,很多 bug 都卡在这条线上。


















