golang指针

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

golang-point

一、指针的声明

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

package main
import (
    "fmt"
)
func main() {
    b := 255
    var a *int = &b
    fmt.Printf("Type of a is %T\n", a)
    fmt.Println("address of b is", a)
}

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

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

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

二、指针的空值与计算

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

package main
import (
    "fmt"
)
func main() {
    a := 25
    var b *int
    if b == nil {
        fmt.Println("b is", b)
        b = &a
        fmt.Println("b after initialization is", b)
    }
}

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

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

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

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

package main
func main() {
    b := [...]int{109, 110, 111}
    p := &b
    p++
}

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

三、指针的解引用

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

package main
import (
    "fmt"
)
func main() {
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
}

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

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

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

package main
import (
    "fmt"
)
func main() {
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
    *a++
    fmt.Println("new value of b is", b)
}

执行后输出结果如下:

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

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

四、传递指针给函数

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

package main
import (
    "fmt"
)
func change(val *int) {
    *val = 55
}
func main() {
    a := 58
    fmt.Println("value of a before function call is", a)
    b := &a
    change(b)
    fmt.Println("value of a after function call is", a)
}
# 执行结果如下
E:\golang\point>go run 06.go
value of a before function call is 58
value of a after function call is 55

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

package main
import (
    "fmt"
)
func modify(arr *[3]int) {
    (*arr)[0] = 90
}
func main() {
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

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

package main
import (
    "fmt"
)
func modify(arr *[3]int) {
    arr[0] = 90
}
func main() {
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

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

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

package main
import (
    "fmt"
)
func modify(sls []int) {
    sls[0] = 90
}
func main() {
    a := [3]int{89, 90, 91}
    modify(a[:])
    fmt.Println(a)
}

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

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注