go中传* t是值传递,复制的是8字节地址而非整个结构体;小结构体传值更安全清晰,大结构体(>32字节)传指针才省;返回局部变量地址由逃逸分析保障安全,但需警惕gc压力;nil检查取决于函数契约而非语法。

Go 函数参数传指针时,值到底有没有被复制?
传 *T 本身是值传递——复制的是指针变量的地址值(通常 8 字节),不是它指向的整个 T 结构体。这意味着:小结构体传值未必慢,大结构体传指针才真省;但若函数内部只读、且 T 不大(比如 struct{a,b int}),传值反而更清晰、更安全。
常见错误是以为“只要结构体大就必须传指针”,结果导致不必要的 nil 检查、意外的副作用(比如函数内修改了原对象)。
- 传
*T:适合需修改原值、或T超过 32 字节(经验值,与 CPU 缓存行对齐相关) - 传
T:适合只读、T小、或希望明确隔离(避免隐式共享) - 接口类型(如
io.Reader)本身已是指针语义,无需额外取地址
返回局部变量地址会不会出问题?
不会。Go 编译器会做逃逸分析(escape analysis),自动把本该分配在栈上的局部变量提升到堆上,确保地址有效。例如:
func newPoint() *Point {
p := Point{X: 1, Y: 2} // p 看似栈变量,但会被逃逸到堆
return &p
}
运行 go build -gcflags="-m" file.go 可验证:若输出 ... moved to heap,说明逃逸成功。
立即学习“go语言免费学习笔记(深入)”;
- 不必手动 new 或刻意避免返回地址——Go 会处理
- 但过度依赖逃逸可能掩盖性能问题:频繁堆分配会增加 GC 压力
- 若返回的是字面量地址(如
&struct{int}{42}),同样安全,也逃逸
函数返回指针时,调用方要不要检查 nil?
取决于函数约定,不是语法强制。Go 标准库多数情况下:成功必非 nil,失败返回 err != nil 且指针为 nil(如 json.Unmarshal、os.Open)。但也有例外:有些函数设计为“零值可接受”,返回非 nil 的零值指针(如 sync.Pool.Get 可能返回 &T{})。
- 看文档或源码——不能只凭类型推断是否要判
nil - 若函数签名是
func() (*T, error),惯例是err != nil时*T为nil,此时先判err即可 - 若返回
*T无 error,比如构造函数NewT(),按惯例应保证非 nil(除非文档明确说可能 nil)
接收者用指针还是值,和返回值优化有什么关系?
没有直接关系,但两者共同影响内存布局和调用开销。接收者类型决定方法调用时是否复制整个值;而返回值类型决定调用方拿到的是新副本还是已有对象引用。
典型陷阱:一个方法接收者是 *T,内部修改字段后返回 *T,调用方误以为得到的是新对象,实际只是原对象地址——这不算 bug,但若逻辑依赖“返回新实例”,就会出错。
- 如果方法名含
Copy、Clone、DeepCopy,返回值应是新分配的*T或T,且接收者通常用T(避免意外改原值) - 如果方法名是
SetX、Reset,接收者用*T更自然,返回值一般不返回指针(除非链式调用需要) - 混用值/指针接收者会导致同一个类型无法同时满足多个接口(因方法集不同)
* 符号决定**。










