Go教程第十篇:指针

指针

在这篇教程中,我们将学习Go里面的指针是如何工作的,以及Go的指针和C/C++的区别和差异。

什么是指针 ?

指针图示

指针也是一个变量,它用于存储其他变量的内存地址。在上面的图示中,b变量的值为156,它被存储于内存地址0x1040a124位置上。变量a保存着b的地址。这时,我们就说,a指向了b。

声明指针

*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)
}

&操作符可用于获取一个变量的地址。在上面的程序中,我们把变量b的地址赋值给a。这也就是说,a指向了b。如果我们要打印a的值的话,那么输出的就是b的地址。程序的输出如下:

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

指针的零值

指针的零值是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)
    }
}

在上面的程序中,b会被初始化为nil。然后被赋上a的值。程序会输出如下:

b is   
b after initialisation is 0x1040a124

使用new函数创建指针

Go提供了一个很方便的函数new,可用于创建指针。new函数会接收一个类型作为参数,同时返回一个指向该类型的零值的指针。

package main

import (
    "fmt"
)

func main() {
    size := new(int)
    fmt.Printf("Size value is %d, type is %T, address is %v\n", *size, size, size)
    *size = 85
    fmt.Println("New size value is", *size)
}

在上面的程序中,我们使用new()函数创建了一个int类型的指针。此函数会返回一个int类型的指针,它指向int类型的零值。因为int类型的零值是0,所以size的类型为 *int,其值为0。例如 *size输出的是0。

上面程序的输出结果是:

Size value is 0, type is *int, address is 0x414020
New size value is 85

引用指针

引用指针也就意味着访问指针指向的变量的值。语法为:*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的值。程序的输出如下:

address of b is 0x1040a124
value of b is 255

我们再写一个程序,使用指针改变b的值。

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)
}

在上面的程序中,我们使用*a++让a指向的变量的值自增1.因此b的值此时变成256。程序的输出如下:

address of b is 0x1040a124  
value of b is 255  
new value of b is 256 

指针作为函数参数传递

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)
}

在上面的程序中,我们把指针变量b传递给函数change。在change函数内部,我们利用指针引用改变了
a的值。此程序的输出如下:

value of a before function call is 58
value of a after function call is 55

指针作为返回值

函数返回一个局部变量的指针是完全合法的。Go的编译器是非常智能的,它会在堆内存上分配一个变量。

package main

import (
    "fmt"
)

func hello() *int {
    i := 5
    return &i
}
func main() {
    d := hello()
    fmt.Println("Value of d", *d)
}

在上面的程序中,hello函数返回了局部变量i的地址。在C和C++中没有定义上述代码的行为。
但是在Go中,编译器会进行逃逸分析,当该地址离开了局部作用域时,便会在堆上为i分配内存。
因此,该程序将输出如下结果:

 Value of d 5

不要把指针作为函数参数传给数组,而要使用slice

我们现在假定要在函数内部修改数组,并且在函数内部对数组做出的修改对调用者必须可见。要做到这一点,可以把指针作为实参传递给函数的数组形参。

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)
}

在上面的程序中,我们把数组a的地址传递给了modify()函数。在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中惯用的方式,我们可以使用slice来做这件事。

我们来用slice复写上面的程序。

package main

import (  
    "fmt"
)
func modify(sls []int) {  
    sls[0] = 90
}

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

在上面的程序中,我们把一个slice传递个函数,在modify函数内部我们把slice第一个元素的值改为90。它的输出结果也是:[90 90 91]。因此,在Go中我们推荐使用slice来做。

Go不支持指针运算

和C和C++不同,Go并不支持指针运算。

package main

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

上面的程序会抛出编译错误:main.go:6: invalid operation: p++ (non-numeric type *[3]int)
我们上面所讨论的这些东西,都可以在Github上找到。github仓库地址。
以上就是关于Go指针的有关说明,祝您愉快!

你可能感兴趣的:(Go教程第十篇:指针)