指针

指针保存某个变量的内存地址。通过指针可以间接读取或修改该变量的值,并避免大对象在函数间传递时的完整拷贝。

取地址与解引用

& 获取变量地址(取地址),* 通过地址访问目标值(解引用):

package main

import "fmt"

func main() {
    a := 10
    p := &a // p 的类型是 *int,指向 a

    fmt.Println(p)  // 0xc000014098(地址,每次运行不同)
    fmt.Println(*p) // 10:通过指针读取 a 的值

    *p = 20 // 通过指针修改 a
    fmt.Println(a) // 20
}

指针类型写作 *T(如 *int*User),表示"指向 T 的指针"。

为什么需要指针:值传递语义

Go 的函数参数与赋值均为值拷贝。函数内部修改参数副本不会影响原值;需要跨函数修改或避免拷贝时,必须传递指针:

package main

import "fmt"

// 值参数:修改的是副本
func tryUpdateByValue(u User) {
    u.Age = 99
}

// 指针参数:修改作用于原变量
func updateByPointer(u *User) {
    u.Age = 99 // Go 自动解引用,等价于 (*u).Age = 99
}

type User struct {
    Name string
    Age  int
}

func main() {
    u := User{Name: "Alice", Age: 30}

    tryUpdateByValue(u)
    fmt.Println(u.Age) // 30:未变化

    updateByPointer(&u)
    fmt.Println(u.Age) // 99:已修改
}

:::tip 选择值还是指针的实践经验:

  • 需要修改原值、或结构体较大需要避免拷贝时,传指针。
  • 小型只读数据(如 time.Time)传值更安全,可避免调用方数据被意外修改。
  • 切片、映射本身是引用语义的结构,通常无需再取指针。 :::

nil 指针与安全性

未指向任何变量的指针为零值 nil。对 nil 指针解引用会触发 panic(运行时崩溃):

package main

import "fmt"

func main() {
    var p *int // nil
    fmt.Println(p == nil) // true

    if p != nil {
        fmt.Println(*p)
    } else {
        fmt.Println("p 未初始化") // 安全检查后再解引用
    }
    // _ = *p // 解引用 nil 指针:panic: invalid memory address
}

nil 指针也有惯用价值:结构体方法的指针接收者可以为 nil 并在方法内部处理,例如标准库中 (*bytes.Buffer)(nil) 场景的空判断。

new 与指针的创建

& 外,new(T) 分配一块零值的 T 内存并返回其指针:

p := new(int)      // *int,指向 0
q := new(User)     // *User,各字段为零值
_ = p
_ = q

new 适用于"先要一个零值指针"的场景;有初始值时通常直接写 &User{Name: "Alice"}

没有指针运算

与 C 不同,Go 不支持对指针做加减运算(p++p+1 均为编译错误),也不支持指针与整数互转。这一设计牺牲了少量灵活性,换取了内存安全与垃圾回收的高效实现。

与后续章节的关联

  • 指针接收者与值接收者的差异详见结构体与方法
  • 接口变量持有指针还是值会影响方法集,详见接口
  • 并发写共享数据时,指针指向的内容同样需要同步保护,详见sync 包

小结

  • & 取地址,* 解引用;指针类型为 *T
  • Go 一切传参皆拷贝;跨函数修改或避免大对象拷贝时使用指针。
  • nil 指针解引用会 panic,解引用前必须判空;Go 无指针运算。