go函数参数均为值传递,传值类型无法修改原变量,传指针可修改;结构体大小影响性能,小结构体可值传,大或含slice/map的应指针传;接口实现需匹配接收者类型;nil指针必须判空防panic。

函数传参时改不动原变量?先看参数是值还是指针
Go 里没有“引用传递”,所有函数参数都是值传递——但传的是“什么的值”,决定了你能不能改到原始数据。传 int,就是复制一个整数;传 *int,就是复制一个内存地址(8 字节),而这个地址指向的还是原来的变量。
- 值类型参数(如
func f(p Person)):函数内对p.Name赋值,不影响调用方的Person实例 - 指针类型参数(如
func f(p *Person)):p.Name = "X"直接修改原始结构体字段 - 常见错误现象:
updateName(p); fmt.Println(p.Name)还是旧值 → 检查函数签名有没有*,调用时有没有加& - 即使结构体只有两个字段,只要方法里要改它,就该用指针传参;否则语义上就“承诺不修改”,后续维护更清晰
结构体一大,传值就慢?性能差距不是“可能”,而是“必然”
一个含 [1024]int 的结构体,值传递要拷贝 8KB;指针传递永远只拷贝 8 字节。这不是微优化,是高频调用下立刻可见的瓶颈。
- 小结构体(比如
type Point struct{ X, Y int }):值传参可读性高、无意外修改风险,可以接受 - 中大型结构体(字段含
[]byte、map[string]int、嵌套 struct):一律用*MyStruct,别犹豫 - 不确定大小?看定义:只要结构体里有 slice/map/chan —— 即使结构体本身小,内部共享数据也可能很大,优先指针
- 基准测试工具
go test -bench=.能直接验证差异,例如BenchmarkPassLargeStructByValuevsBenchmarkPassLargeStructByPointer
接口赋值失败?很可能是方法集和接收者类型不匹配
接口是否能接收某个类型,不只看方法名和签名,更关键的是接收者类型。值类型 T 和指针类型 *T 的方法集不同,这是最容易被忽略的隐性规则。
-
func (u User) GetName() string→ 只属于User的方法集;User{}可赋值给接口,&User{}也可以 -
func (u *User) SetName(n string)→ 只属于*User的方法集;User{}无法赋值给含SetName的接口,会编译报错 - 常见错误:
var i Namer = User{}报错 “User does not implement Namer (SetName method has pointer receiver)” - 解决办法:统一用指针接收者,或确保接口只包含值接收者方法;不要混用,尤其在导出 API 时
nil 指针解引用 panic?不是 bug,是你忘了判空
Go 不做运行时空指针防护。一旦对 nil 指针执行 *p 或调用其方法,程序立即 panic。这不是语言缺陷,而是设计取舍:明确要求开发者主动处理边界。
立即学习“go语言免费学习笔记(深入)”;
- 返回指针的函数(如
json.Unmarshal解析结构体、db.QueryRow扫描)必须检查是否为 nil - 常见错误写法:
if p.Name != ""前没判p != nil,一触发就崩溃 - 安全习惯:所有指针参数进入函数第一行就做非空校验,或用
if p == nil { return }快速失败 - 并发场景下更要小心:多个 goroutine 共享指针时,值类型天然线程安全,指针则需额外同步(如
sync.Mutex)
最常被低估的点是:值类型和指针类型不只是“能不能改原值”的区别,它牵扯到方法集、接口兼容性、内存布局、并发安全、甚至 JSON 序列化行为(如 nil slice 和空 slice 在编码时表现不同)。选错一次,可能埋下几个小时都找不到的坑。










