很多人第一次学 Go 指针时,会带着两种相反的情绪:

  • 学过 C 的人会觉得它“太温和了”
  • 没学过底层语言的人会觉得它“已经够危险了”

这两种感觉都可以理解。Go 里的指针既保留了“通过地址访问数据”的能力,又刻意拿掉了很多容易把程序写崩的部分。

背景

指针本质上不是“高级语法”,而是一个很直接的概念:
某个值在内存中的位置。

Go 里引入指针,主要是为了解决两个问题:

  • 避免大对象频繁复制
  • 允许你修改原对象,而不是只改副本

也就是说,指针不是为了“炫技”,而是为了明确表达共享和修改。

核心概念

&:取地址

如果你想拿到一个变量的地址,用 &

1x := 10
2p := &x

这里:

  • x 是一个 int
  • p 是一个 *int

也就是“指向 int 的指针”。

*:解引用

如果你想通过指针拿到它指向的值,就要解引用:

1fmt.Println(*p) // 10

如果你给解引用结果赋值,改的就是原对象:

1*p = 20
2fmt.Println(x) // 20

这就是指针最核心的能力:
不是复制一份数据出来改,而是直接改原来的那份数据。

一个最小示例

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    x := 10
 7    p := &x
 8
 9    fmt.Println("x =", x)
10    fmt.Println("*p =", *p)
11
12    *p = 42
13    fmt.Println("x =", x)
14}

运行逻辑很清楚:

  1. 先拿到 x 的地址
  2. 再通过指针读取 x
  3. 最后通过指针修改 x

指针和参数传递

这部分是很多人最容易混的地方。

Go 始终是值传递。
即使你传的是指针,本质上也还是“把这个指针值复制一份传进去”。

但因为这个值本身指向原对象,所以函数里可以通过它修改原对象。

例如:

 1package main
 2
 3import "fmt"
 4
 5func inc(n *int) {
 6    *n++
 7}
 8
 9func main() {
10    x := 1
11    inc(&x)
12    fmt.Println(x) // 2
13}

这里不是“Go 变成了引用传递”,而是“复制了一份指针值进去,这份指针仍然指向原来的 x”。

指针最常见的使用场景

1. 修改结构体内容

1type User struct {
2    Name string
3}
4
5func rename(u *User, name string) {
6    u.Name = name
7}

如果这里不用指针,函数里改到的就是结构体副本。

2. 避免大对象复制

当结构体比较大时,用指针传递可以减少复制开销。

但这不是绝对规则,只有在语义和性能都合适时才值得这么做。

3. 表达“可空”状态

例如一个字段如果需要区分:

  • 没有值
  • 值就是 0

这时常会用指针表达可选值。

Go 指针和 C 指针最关键的区别

Go 刻意砍掉了几样危险能力:

  • 不支持指针算术
  • 不能像 C 那样随意偏移地址
  • 不能任意把指针当整数乱算

这让 Go 指针的使用边界更小,也更可控。

所以 Go 指针更像“安全一点的对象访问入口”,而不是“随便操作内存的工具”。

常见误区

误区一:以为有指针就一定更快

不一定。

指针能减少复制,但也会引入:

  • 更多共享状态
  • 更复杂的生命周期
  • 可能的逃逸和额外 GC 压力

所以不能机械地认为“全都改成指针就会更高效”。

误区二:忘记 nil 指针

指针类型的零值是 nil

如果你直接解引用一个 nil 指针,程序会 panic:

1var p *int
2fmt.Println(*p) // panic

误区三:把“传指针”理解成“引用传递”

Go 不是引用传递。
只是因为你传进去的是一个能找到原对象的地址,所以看起来像“直接改到了外面”。

指针和方法接收者

Go 里还有一个非常常见的指针场景,就是方法接收者:

1type Counter struct {
2    N int
3}
4
5func (c *Counter) Inc() {
6    c.N++
7}

这里用指针接收者,通常是因为:

  • 方法需要修改接收者
  • 或者希望避免复制整个结构体

如果方法不修改状态,是否必须用指针接收者,就要结合类型大小和语义一起看。

我的总结

  • Go 指针的本质是“通过地址访问原对象”
  • & 取地址,* 解引用
  • Go 依然是值传递,传指针也不例外
  • 指针最常用于修改对象、避免大对象复制、表达可空值
  • Go 指针比 C 更克制,没有指针算术
  • 真正用好指针,关键不在语法,而在于清楚什么时候应该共享、什么时候应该复制