Go函数参数永远是值传递,修改副本不影响原变量;要修改原始值必须传指针并解引用,核心依据是是否需共享可变状态或避免大对象拷贝。

为什么修改函数参数时原变量没变
Go 里函数传参默认是值拷贝,哪怕你传的是一个大结构体,func update(s MyStruct) 里的 s 是副本,改了不影响调用方。想让修改生效,必须传指针:func update(s *MyStruct)。这不是“高级技巧”,而是 Go 内存模型的必然要求——没有引用传递,只有显式指针。
常见错误:传了指针但忘了解引用,比如写成 s = &MyStruct{...}(只是改了局部指针变量),而不是 *s = MyStruct{...}(改了指针指向的内存)。
什么时候该用 *T 而不是 T
判断依据不是“对象大不大”,而是“是否需要共享可变状态”:
- 需要在多个函数间协同修改同一份数据 → 用
*T - 方法要改变接收者字段(比如
func (u *User) SetName(n string))→ 接收者必须是*T - 类型含
sync.Mutex或其他非可复制字段 → 只能用指针,否则编译报错cannot be copied - 返回局部变量地址(如
return &v)→ 安全,Go 编译器会自动栈逃逸到堆,不用手动管理
new(T) 和 &T{} 的实际区别
两者都返回 *T,但初始化行为不同:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
new(T)分配零值内存,返回指向它的指针,比如new(int)得到*int指向0 -
&T{}是字面量取地址,支持字段初始化:&User{Name: "Alice"},而new(User)只能得到所有字段为零值的实例 - 性能无差异,选哪个纯看语义:要零值就用
new,要自定义初值就用&T{}
别写 var p *T; p = new(T),直接 p := new(T) 更清晰。
nil 指针解引用 panic 的典型场景
最常发生在未检查接口/切片/映射字段是否为空就直接解引用:
- 结构体字段是
*string,但 JSON 解析后该字段为null→ 对应 Go 字段是nil,*spanic - 函数返回
*Config,但初始化失败返回nil,调用方没判空就直接c.Timeout - 用
make([]T, 0)创建空切片没问题,但var s []T是 nil 切片,len(s)虽安全,s[0]就 panic
Go 不做空指针防护,所有 *T 使用前,只要来源不可信(JSON、DB、用户输入、第三方库返回),就必须显式判空:if p != nil { ... }。
指针本身不难,难的是时刻意识到“这个 *T 是谁分配的、生命周期到哪、会不会是 nil”。Go 把内存所有权交还给程序员,只是换了一种更直白的方式:不靠 GC 隐藏细节,而靠编译期约束和运行时 panic 强迫你面对它。

















