go指针本质是存内存地址的变量,核心操作为声明t、取地址&、解引用;需注意nil检查、生命周期管理及逃逸分析影响。

怎么声明、取地址、解引用——三步搞清指针本质
Go 里的指针就是存「内存地址」的变量,不是值本身。它只有三个核心动作:& 取地址、* 解引用、*T 声明类型。
& 只能作用于变量(比如 &x),不能对字面量或表达式用(&42 或 &(a+b) 都报错);* 有两种语境:在声明时是类型修饰符(var p *int),在使用时是解引用操作(*p = 10)——别混淆上下文。
- 未初始化的指针默认是
nil,直接fmt.Println(*p)就 panic - 想安全解引用,必须先判空:
if p != nil { fmt.Println(*p) } - 函数返回指针时,调用方要主动检查是否为
nil,尤其像json.Unmarshal等可能失败的场景
传指针是为了改原值还是省拷贝?别乱用
传指针的核心目的就两个:修改调用方的原始数据,或避免大结构体复制开销。但不是所有情况都该用。
- 基础类型(
int、string)传指针几乎没收益,还增加nil判断负担 - 结构体字段多、体积大(比如含切片、map、嵌套结构)时,
*User比User传参更高效 - 方法接收者用指针(
func (u *User) SetName())才能修改字段;值接收者只能读,且每次调用都拷贝整个结构体
例如:type Config { Timeout int; Hosts []string },传 *Config 能避免复制底层数组和字符串头,而传值可能让一次调用多分配几百字节。
立即学习“go语言免费学习笔记(深入)”;
map 和切片里存指针,最容易踩哪几个坑?
把 *T 存进 map[string]*T 或 []*T 很常见,但循环中一不留神就全指向同一个地址。
- 循环变量复用地址:写
for _, name := range names { u := User{Name: name}; m[name] = &u }→ 所有 key 都指向最后一次迭代的u - 正确做法是每次迭代新建对象并取地址:
m[name] = &User{Name: name}(Go 逃逸分析会自动分配到堆) -
[]*T切片本身不共享底层数组,但多个元素若指向同一结构体,修改一个会影响所有——这是预期行为还是 bug,得看业务逻辑
错误示例中,users[name] = &u 的 u 是栈上变量,每次迭代被覆盖;正确写法用字面量构造,Go 编译器识别为需逃逸,自动分配到堆。
指针生命周期管理:别让“不崩溃”骗了你
Go 编译器会做逃逸分析,比如 func getPointer() *int { x := 10; return &x } 不会崩溃,因为 x 被挪到堆上了。但这只是“不崩溃”,不是“写得对”。
- 返回局部变量地址,掩盖了设计问题:调用方误以为这个指针指向一个可长期持有的、独立管理的对象
- 长期持有指针(如存在全局
map中)会阻止 GC 回收底层对象,造成逻辑上的内存泄漏 - 并发场景下,多个 goroutine 共享同一指针又无锁保护,
-race会立刻报出数据竞争
真正关键的不是“能不能用指针”,而是每一次 &、*、传递,是否清楚它的生命周期边界在哪里。逃逸分析帮你躲过了崩溃,但没帮你理清责任归属。










