go指针是表达共享或修改意图的语法信号,仅能作用于可寻址变量;字面量、map值、函数返回值等不可取地址;*t是类型,*是操作符;传指针须明确为修改原值或避免大对象拷贝;nil指针解引用必panic,需显式检查。

Go 语言里指针不是“高级技巧”,而是明确表达“我要共享或修改这个值”的语法信号。用错不是语法难,是没分清什么时候真需要它。
为什么 & 取地址会编译失败
常见错误:cannot take the address of 42、cannot take the address of m["k"]、cannot take the address of getValue()。
根本原因:& 只能作用于“可寻址的变量”——即有固定内存位置、生命周期明确的具名变量。
- 字面量(
42、"hello")、常量、函数返回值(除非返回的是变量本身)不可寻址 -
map的 value 是临时拷贝,&m["k"]不合法;要改 map 元素,直接m["k"] = v - 切片、
map、chan本身已是引用类型,传它们的副本就能改底层数据,不需要&slice - 结构体字段若未导出或嵌套过深(如
s.f.g.h),中间某层不可寻址时,整条链路也不能取地址
*T 是类型,* 是操作符,别混在一起读
写 var p *int 时,*int 是一个完整类型名,和 []string、func(int) bool 一样,是声明的一部分;而解引用时的 *p 中,* 是独立的操作符,跟 +、- 同级。
容易踩的坑:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 误以为
var p *int; p = 42合法 —— 实际上p要存地址,不能赋整数 - 在函数签名里写成
func f(* p int)—— 语法错误,*必须紧贴类型:func f(p *int) - 看到
*[3]int就当成“数组指针”,其实它是“指向长度为 3 的数组的指针”,和[]int完全不兼容,也不能直接传给期望切片的函数
传指针给函数前,必须确认两个目的之一成立
Go 函数参数默认值传递,传指针只有两种合理动机:
- 函数内必须修改调用方的原始变量(比如
increment(&n)、setName(&p, "A")) - 参数是大结构体(如含大数组、多字段嵌套),拷贝开销显著(一般 > 128 字节建议测一下逃逸分析)
反例:
- 传
*int改一个int—— 值传递更快,还省去 nil 检查 - 传
*[]int给想扩容切片的函数 —— 切片头本身轻量,扩容只需返回新切片;传*[]int反而掩盖了语义,且无法真正影响原变量(除非你再赋值回去) - 结构体方法接收者统一用指针,却不考虑是否真要改字段 —— 值接收者更轻量,且对小结构体无性能损失
nil 指针解引用不是 bug,是设计强制你检查
var p *int 声明后就是 nil,此时 *p = 5 或 fmt.Println(*p) 会立即 panic:invalid memory address or nil pointer dereference。
这不是 Go 的缺陷,而是它拒绝隐式空指针防护的体现。真实场景中必须显式判断:
- 函数返回指针(如
findUser(id)),调用后必须if u != nil { ... } - 接口变量底层是
(*T)(nil)时,调方法也会 panic(如var s io.Writer = (*bytes.Buffer)(nil); s.Write(...)) - 用
reflect解引用前,得先reflect.ValueOf(ptr).Kind() == reflect.Ptr && !reflect.ValueOf(ptr).IsNil()
最易忽略的一点:结构体字段如果是指针类型,初始化时不会自动分配内存,struct{ Name *string } 的 Name 默认是 nil,直接 *s.Name 就崩 —— 这类字段往往需要配合构造函数或显式 new(string) 初始化。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










