什么是指针,指针是存储一个变量的内存地址的变量。在下图,变量 b 的值是 156,存储在地址为 0xc0000100a8的内存中。变量 a 存储了变量 b 的地址。现在可以说 a 指向 b。

golang-point
golang-point

一、指针的声明

指向类型 T 的指针用 *T 表示。先看一个测试代碱:

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    b := 255
 7    var a *int = &b
 8    fmt.Printf("Type of a is %T\n", a)
 9    fmt.Println("address of b is", a)
10}

& 操作符用来获取一个变量的地址。在上面的程序中,第 9 行(var定义所在行)我们将 b 的地址赋给 a(a 的类型为 *int)。现在我们说 a 指向了 b。当我们打印 a 的值时,b 的地址将会被打印出来。程序的输出为:

1Type of a is *int
2address of b is 0xc0000100a8

你可能得到的是一个不同的 b 的地址,因为 b 可以在内存中的任何地方。

二、指针的空值与计算

指针的空值为 nil ,其一般用于进行判断对比操作。代码如下:

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    a := 25
 7    var b *int
 8    if b == nil {
 9        fmt.Println("b is", b)
10        b = &a
11        fmt.Println("b after initialization is", b)
12    }
13}

上面的代码执行后,结果如下:

1E:\golang\point>go run 02.go
2b is <nil>
3b after initialization is 0xc0000100a8

该程序中,b 的初始值为 nil。接着将 a 的地址赋值给 b。

Go 中的指针只能进行比较运算,不支持其他语言(比如C)中的指针运算,如下代码:

1package main
2func main() {
3    b := [...]int{109, 110, 111}
4    p := &b
5    p++
6}

上面的程序将报错:main.go:6: invalid operation: p++ (non-numeric type *[3]int) 。

三、指针的解引用

解引用指针的意思是通过指针访问被指向的值。指针 a 的解引用表示为:*a。先看下面的代码:

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    b := 255
 7    a := &b
 8    fmt.Println("address of b is", a)
 9    fmt.Println("value of b is", *a)
10}

上面程序的第10行,我们将 a 解引用并打印这个解引用得到的值。和我们预期的一样,程序打印的是 b 的值。程序的输出为:

1E:\golang\point>go run 04.go
2address of b is 0xc000062090
3value of b is 255

上面有提到,golang语言不能像C语言一样对指针进行计算操作,不过其可以对其解引用进行计算操作,如下:

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    b := 255
 7    a := &b
 8    fmt.Println("address of b is", a)
 9    fmt.Println("value of b is", *a)
10    *a++
11    fmt.Println("new value of b is", b)
12}

执行后输出结果如下:

1E:\golang\point>go run 05.go
2address of b is 0xc0000100a8
3value of b is 255
4new value of b is 256

从执行结果可以看出,我们将 a 指向的值自增 1,这样做也改变了 b 的值,因为 a 指向 b。因此 b 的值变为 256。

四、传递指针给函数

下面我们演示的代码是将指向 a 的指针 b 传递给函数 change。在函数 change 内部,通过解引用修改了 a 的值,代码如下:

 1package main
 2import (
 3    "fmt"
 4)
 5func change(val *int) {
 6    *val = 55
 7}
 8func main() {
 9    a := 58
10    fmt.Println("value of a before function call is", a)
11    b := &a
12    change(b)
13    fmt.Println("value of a after function call is", a)
14}
15# 执行结果如下
16E:\golang\point>go run 06.go
17value of a before function call is 58
18value of a after function call is 55

需要注意的是不要传递指向数组的指针给函数,而是使用切片。假设我们需要通过函数修改一个数组。一个办法是将数组的指针作为参数传递给函数。

 1package main
 2import (
 3    "fmt"
 4)
 5func modify(arr *[3]int) {
 6    (*arr)[0] = 90
 7}
 8func main() {
 9    a := [3]int{89, 90, 91}
10    modify(&a)
11    fmt.Println(a)
12}

在上面的程序中,第13行,数组 a 的地址传递给了函数 modify。在第8行的 modify 函数中,我们通过解引用的方式将数组的第一个元素赋值为 90。程序输出为:[90 90 91]。a[x] 是 (*a)[x] 的简写,因此上面的程序中,(*arr)[0] 可以替换为 arr[0]。让我们用这种简写方式重写上面的程序:

 1package main
 2import (
 3    "fmt"
 4)
 5func modify(arr *[3]int) {
 6    arr[0] = 90
 7}
 8func main() {
 9    a := [3]int{89, 90, 91}
10    modify(&a)
11    fmt.Println(a)
12}

程序的输出依然是:[90 90 91] 。

虽然可以通过传递数组指针给函数的方式来修改原始数组的值,但这在 Go 中不是惯用的方式,我们可以使用切片完成同样的事情。让我们用切片的方式重写上面的程序:

 1package main
 2import (
 3    "fmt"
 4)
 5func modify(sls []int) {
 6    sls[0] = 90
 7}
 8func main() {
 9    a := [3]int{89, 90, 91}
10    modify(a[:])
11    fmt.Println(a)
12}

在上面的程序中,第13行,我们传递了一个切片给 modify 函数。在函数内部,切片的第一个元素被修改为 90。程序的输出为:[90 90 91]。所以请不要以数组指针作为参数传递给函数,而是使用切片:)。这样的代码更加简洁,在 Go 中更常被使用。