Go语言常见陷阱包括:nil与空切片行为差异、for range中变量地址复用、defer闭包捕获变量时机错误、结构体未导出字段无法JSON序列化;应统一用make([]T, 0)初始化、循环内重声明变量取址、defer传参捕获值、导出字段首字母大写。

Go 语言上手快,但很多坑藏在「看起来理所当然」的地方——比如 nil 切片和空切片行为不一致、for range 循环中取地址复用、defer 和闭包捕获变量时机错位。这些不是语法错误,编译能过,运行时才暴露,且难以复现。
切片赋值时别直接用 nil 当“空”来用
很多人初始化切片写 var s []int,以为和 s := []int{} 一样安全。其实不然:nil 切片的底层数组指针为 nil,而空切片([]int{})有合法底层数组(长度 0,容量可能 >0)。对 nil 切片做 append 没问题,但若传给需要非 nil 底层的函数(如某些 Cgo 封装、json.Unmarshal 的预分配逻辑),会 panic 或静默失败。
- 统一用
s := make([]int, 0)或s := []int{}初始化空切片,避免裸var s []T - 判断是否为空,优先用
len(s) == 0,而非s == nil—— 因为[]int{}不是nil但也是空的 - 如果函数明确要求非
nil切片(比如要取&s[0]),进函数前加if len(s) == 0 { s = make([]T, 0) }
for range 中取变量地址会复用同一个内存地址
这是最常被踩的坑之一:循环里把 &v 存进 map 或 slice,结果所有指针都指向最后一次迭代的 v 值。
values := []string{"a", "b", "c"}
m := make(map[string]*string)
for _, v := range values {
m[v] = &v // ❌ 全部指向同一个 v 变量地址
}
// 最终 m["a"], m["b"], m["c"] 都指向 "c"
- 正确做法:在循环内声明新变量,再取其地址:
for _, v := range values { v := v; m[v] = &v } - 或者直接用索引:
for i := range values { m[values[i]] = &values[i] }(前提是values不会被重切或扩容) - Go 1.22+ 对此有静态分析提示(
loop variable v captured by func literal类似警告),但旧版本不会报
defer 闭包里读取的变量是执行时的值,不是定义时的值
defer 注册的是函数值,但闭包捕获的是变量引用。如果 defer 前变量被修改,执行时看到的就是修改后的值。
立即学习“go语言免费学习笔记(深入)”;
i := 0
defer func() { println(i) }() // 输出 1,不是 0
i = 1
- 想捕获当前值,必须显式传参:
defer func(val int) { println(val) }(i) - 在循环中 defer,尤其要注意:不要在 for 外定义变量然后 defer 它;应把关键值作为参数传入 defer 函数
- 常见误用场景:数据库事务 rollback、文件 close、锁 unlock —— 如果 defer 依赖的变量后续被改写,可能关错文件或释放错锁
结构体字段导出与否影响 JSON 序列化和反射行为
Go 的 json.Marshal 默认只序列化导出字段(首字母大写),即使你写了 json:"xxx" tag。小写字母开头的字段永远被忽略,连 tag 都不生效。
type User struct {
Name string `json:"name"` // ✅ 导出 + tag → "name":"xxx"
age int `json:"age"` // ❌ 未导出 → 字段完全消失,tag 无效
}
- 字段名首字母必须大写才能被
json、xml、gob等标准库编码器识别 - 反射操作(如
reflect.Value.FieldByName)同样只返回导出字段;想访问私有字段需用FieldByIndex+ 偏移,但不推荐用于业务逻辑 - 别指望靠 tag “绕过”导出规则 —— tag 是修饰符,不是权限开关
这些坑往往出现在重构、并发、序列化、资源清理等真实场景里,单测容易漏掉,线上才浮现。最稳妥的做法不是靠记忆,而是在团队代码规范里强制写法(比如禁用裸 var s []T、所有 defer 必须带参数传值),再配合静态检查工具(如 staticcheck)提前拦截。


















