
本文深入讲解 Go 中取地址符 & 和解引用符 * 的本质区别、使用场景及常见误区,结合 json.Decode 等典型用例,帮助开发者准确理解指针机制,避免因混淆操作符导致的编译错误或运行时 panic。
本文深入讲解 go 中取地址符 `&` 和解引用符 `*` 的本质区别、使用场景及常见误区,结合 `json.decode` 等典型用例,帮助开发者准确理解指针机制,避免因混淆操作符导致的编译错误或运行时 panic。
在 Go 语言中,& 和 * 是一对紧密关联但语义完全相反的操作符,它们共同构成了指针操作的基础。理解二者不是“可互换的语法糖”,而是方向相反的语言原语,是写出健壮、高效 Go 代码的关键。
&:取地址操作符(Address-of Operator)
&x 表示“获取变量 x 在内存中的地址”,其结果是一个指针类型值。该操作要求 x 必须是可寻址的(例如:变量、结构体字段、切片元素),不能对字面量、常量或函数调用结果取地址。
y := 42 addr := &y // addr 的类型是 *int,值为 y 的内存地址
*:解引用操作符(Dereference Operator)
*p 表示“访问指针 p 所指向的内存地址中存储的值”,即“顺着指针跳转一次”。该操作要求 p 必须是有效的指针类型变量(且非 nil),否则会导致 panic。
var p *int = &y val := *p // val 的类型是 int,值为 42(即 y 的值)
⚠️ 注意:* 仅用于读取或写入指针目标值;它不用于类型声明中的指针类型定义——那里的 * 是类型构造符(如 *string),属于类型语法的一部分,而非运行时操作。
典型场景:为什么 json.Decode(&u) 必须加 &?
回顾问题中的代码:
var u User
if err := decoder.Decode(&u); err != nil { /* ... */ }
json.Decode 函数签名是 func (d *Decoder) Decode(v interface{}) error,其参数 v 实际上必须是一个可寻址的指针(如 *User),因为 Decode 需要修改传入变量的内存内容(将 JSON 数据反序列化填充到 u 中)。若传入 u(值本身),Go 会传递 u 的副本,Decode 修改的是副本,原始 u 不受影响——这显然不是预期行为。
因此:
- ✅ &u:提供 u 的地址,Decode 可安全写入;
- ❌ u:仅传递值拷贝,无法修改原变量;
- ❌ *u:非法!u 是 User 类型,非指针,不可解引用。
指针声明 vs. 解引用:两个 * 的语境差异
初学者易混淆类型声明中的 * 与表达式中的 *:
var p *int // 声明:p 是一个「指向 int 的指针」类型变量(* 是类型构造符) p = &someInt // 赋值:p 存储 someInt 的地址 val := *p // 解引用:获取 p 所指内存中的 int 值(* 是运行时操作符)
多级指针与安全实践
Go 支持多级指针(如 **int),但应谨慎使用。以下代码演示了地址链与解引用链的对应关系:
package main
import "fmt"
func main() {
y := 100
p := &y // *int
pp := &p // **int(指向指针的指针)
fmt.Printf("y = %d\n", y) // 100
fmt.Printf("p = %p\n", p) // 地址(如 0xc0000140a0)
fmt.Printf("*p = %d\n", *p) // 100(解引用一次)
fmt.Printf("pp = %p\n", pp) // 另一地址(如 0xc000006028)
fmt.Printf("*pp = %p\n", *pp) // 同 p 的地址
fmt.Printf("**pp = %d\n", **pp) // 100(解引用两次)
// 安全检查:解引用前确保指针非 nil
if pp != nil && *pp != nil {
fmt.Println("Safe to dereference: ", **pp)
}
}
关键总结与最佳实践
- ✅ &x 用于获取地址:当函数需要修改实参(如 json.Unmarshal, fmt.Scanf, 自定义 setter 方法)时,必须传 &x。
- ✅ *`p用于访问值**:仅在持有有效指针变量后,用*p` 读/写其所指数据。
- ❌ *禁止对非指针变量使用 `**:*y(y是int`)是编译错误。
- ⚠️ nil 指针解引用会 panic:使用 *p 前建议判空(尤其来自外部输入或可选参数)。
- ? new(T) 等价于 &T{}:二者均返回 *T,但语义不同——new 初始化零值,&T{} 支持字段初始化。
掌握 & 与 * 的精确语义,不仅能消除编译错误,更能让你清晰建模数据所有权与内存访问路径,这是写出地道 Go 代码的基石。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











