本文深入解析 Go 中取地址符 & 和解引用符 * 的语义、作用机制与典型应用场景,结合 json.Decode 等常见用例,厘清何时用 &x、何时用 *T,避免因混淆指针声明与指针操作导致的编译错误。
本文深入解析 go 中取地址符 `&` 和解引用符 `*` 的语义、作用机制与典型应用场景,结合 `json.decode` 等常见用例,厘清何时用 `&x`、何时用 `*t`,避免因混淆指针声明与指针操作导致的编译错误。
在 Go 语言中,& 和 * 是指针操作的核心符号,但它们扮演完全不同的角色:& 是取地址运算符(生成指针),* 是解引用运算符(访问指针所指向的值);同时 *T 还是类型声明语法,表示“指向类型 T 的指针”。理解三者的语境差异,是写出健壮、可维护 Go 代码的关键。
✅ &x:获取变量的内存地址(创建指针值)
& 作用于一个已存在的变量,返回其内存地址,结果是一个指针值(即 *T 类型)。例如:
age := 28 addr := &age // addr 的类型是 *int,值为 age 所在内存地址
这正是你在 json.Decode(&u) 中看到的用法——Decode 函数签名要求传入 interface{},但其内部需修改传入值的字段,因此必须接收一个可寻址的内存位置(即指针)。&u 就是把栈上变量 u 的地址交给 Decode,使其能直接写入数据。
⚠️ 注意:不能对字面量或表达式取地址,如 &42、&(x + y) 在 Go 中非法(编译报错 cannot take the address of ...)。
立即学习“go语言免费学习笔记(深入)”;
✅ *T:指针类型声明(定义变量可存储地址)
当 * 出现在类型位置(如变量声明、函数参数、结构体字段),它表示一种新类型:“指向 T 类型值的指针”。例如:
var u *User // u 是一个指针变量,类型为 *User,初始值为 nil
u = &User{Name: "Alice"} // 合法:赋值一个 *User 类型的值(即地址)此时 *User 不是操作,而是类型名,就像 []string 表示切片类型一样。你声明 u *User,就相当于说:“u 这个变量将来会保存某个 User 实例的内存地址”。
✅ *x:解引用操作(通过地址读/写目标值)
当 * 作用于一个指针变量时,它执行解引用(dereference):从该地址读取或写入其所指向的值。例如:
u := &User{Name: "Alice"}
fmt.Println(u.Name) // ❌ 编译错误:u 是 *User,没有 Name 字段
fmt.Println((*u).Name) // ✅ 正确:先解引用得到 User 值,再访问字段
fmt.Println(u.Name) // ✅ 更简洁!Go 自动解引用结构体指针(语法糖)
*u = User{Name: "Bob"} // ✅ 通过指针修改原值? Go 对结构体指针做了语法优化:u.Name 等价于 (*u).Name,但 *u 本身仍是合法表达式,代表被指向的 User 值。
? 经典对比:&u vs *u vs *User
| 表达式 | 语境 | 含义 | 示例 |
|---|---|---|---|
| &u | 操作符 + 变量 | 获取变量 u 的地址 → *T 值 | json.Decode(&u) |
| *u | 操作符 + 指针变量 | 解引用 u,得到其指向的 T 值 | name := (*u).Name |
| *User | 类型声明 | “指向 User 的指针”这一类型 | func save(u *User) {...} |
? 动手验证:指针链的层级关系
以下代码清晰展示了地址、指针变量、指针的指针之间的关系:
package main
import "fmt"
func main() {
y := 10
var p *int = &y // p 存储 y 的地址
var pp **int = &p // pp 存储 p 的地址
fmt.Printf("y = %d\n", y) // 10
fmt.Printf("&y = %p\n", &y) // y 的地址(如 0xc0000140a0)
fmt.Printf("p = %p\n", p) // p 的值 = &y(同上)
fmt.Printf("*p = %d\n", *p) // 解引用 p → y 的值:10
fmt.Printf("pp = %p\n", pp) // pp 的值 = &p
fmt.Printf("*pp = %p\n", *pp) // 解引用 pp → p 的值(即 &y)
fmt.Printf("**pp = %d\n", **pp) // 二次解引用 → y 的值:10
// 验证等价性
fmt.Println(p == &y) // true
fmt.Println(*pp == p) // true
fmt.Println(**pp == y) // true
}✅ 实际应用建议与常见误区
- json.Decode 必须传 &x:因为函数需要修改 x 的字段。若 x 已是 *User 类型(如 u := new(User) 或 u := &User{}),则直接传 u 即可,无需再加 &(否则 &u 类型是 **User,不匹配)。
-
函数参数设计原则:
- 仅读取小结构体(如 Point{X,Y int})→ 传值(func draw(p Point));
- 需修改原值 或 结构体较大 → 传指针(func scale(p *Point, f float64));
-
避免空指针解引用:使用前务必检查是否为 nil:
if u != nil { fmt.Println(u.Name) } - 不要过度使用指针:Go 鼓励“传递值,复制明确”。只有当需要共享状态、避免拷贝开销或实现接口方法(如 io.Reader)时,才引入指针。
掌握 & 与 * 的语义分层,你就掌握了 Go 内存模型的钥匙——它不复杂,但必须精确。每一次 & 都在建立连接,每一次 * 都在抵达真实;而 *T 则是你为这段连接预先约定的契约。


















