引入

Go 里函数传参是值传递,参数会被复制一份。想修改原始变量或在函数间共享数据时,就需要用到指针。指针保存的是另一个变量的内存地址,通过指针可以间接修改数据,是 Go 里非常核心的概念。

正文

定义

定义

指针是一个变量,它的值不是数据本身,而是另一个变量的内存地址。通过 & 取地址可以获得变量的指针,通过 * 解引用可以访问或修改指针指向的数据。Go 的指针不支持指针运算,比 C 语言安全得多。

语法

// 声明指针变量
var p *int
 
// 取地址
x := 10
p = &x
 
// 解引用(访问/修改指针指向的值)
fmt.Println(*p)  // 读取
*p = 20          // 修改

例子

取地址和解引用

x := 42
p := &x
 
fmt.Println(x)   // 42
fmt.Println(p)   // 0xc000018000(内存地址)
fmt.Println(*p)  // 42(通过指针读取值)
 
*p = 100
fmt.Println(x)   // 100(通过指针修改了原始值)

&x 拿到 x 的地址,*p 通过地址访问或修改 x 的值。

函数传指针

func double(n *int) {
    *n *= 2
}
 
func main() {
    x := 5
    double(&x)
    fmt.Println(x) // 10
}

传指针进函数,函数内修改指针指向的值,原始变量跟着变。值传递做不到这一点。

值传递 vs 指针传递

func byValue(n int) {
    n = 100 // 修改的是副本
}
 
func byPointer(n *int) {
    *n = 100 // 修改的是原始值
}
 
func main() {
    x := 1
    byValue(x)
    fmt.Println(x) // 1(没变)
 
    byPointer(&x)
    fmt.Println(x) // 100(变了)
}

值传递修改副本,指针传递修改原始值。

new() 函数

p := new(int)
fmt.Println(*p) // 0(零值)
*p = 42
fmt.Println(*p) // 42

new(T) 分配一个 T 类型的内存空间,返回指向它的指针。初始值是该类型的零值。

new() 创建结构体指针

type Person struct {
    Name string
    Age  int
}
 
func main() {
    p := new(Person)
    p.Name = "Alice"
    p.Age = 25
    fmt.Println(p.Name, p.Age)
}

new() 创建结构体时返回指针,访问字段用 . 就行,Go 自动处理了解引用。

指针的零值是 nil

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

指针不初始化就是 nil,解引用 nil 指针会触发 panic

多级指针

x := 10
p1 := &x    // *int
p2 := &p1   // **int
 
fmt.Println(**p2) // 10

Go 支持多级指针,但实际开发中很少用到两级以上

常见写法

写法说明
p := &x取地址,获得指针
*p解引用,读取或修改值
new(int)分配内存,返回指针
var p *int声明指针变量(零值 nil)
func f(p *int)函数接收指针参数
p.Name结构体指针访问字段(自动解引用)

特点

  • Go 指针不支持指针运算(不能 p++),比 C 安全
  • 值传递是副本,指针传递可以修改原始数据
  • new() 分配内存并返回指针,初始值为零值
  • 指针的零值是 nil,解引用 nil 指针会 panic
  • 结构体指针访问字段用 .,Go 自动解引用
  • Go 没有引用传递,只有值传递,指针本身也是按值传递的
  • & 取地址,* 解引用,两个操作互为反向

理解

指针就是”变量家的门牌号”。拿到门牌号(&),就能找到变量在哪;通过门牌号进门(*),就能读取或修改里面的东西。Go 的指针设计得很克制,不能做运算,不能随便乱指,大大降低了出错的可能。理解指针是理解 Go 方法、接口和并发编程的基础。

引出

有了指针,就能给类型”绑定”行为。接下来看 Go 方法,学习怎么用值接收者和指针接收者给结构体定义方法。