文章目录

  • 导言
  • 指针
  • 指针是什么?
  • 声明指针
  • 指针的零值
  • 使用 `new`函数 创建指针
  • 指针解引用
  • 将指针传递给函数
  • 从函数中返回指针
  • 数组指针 vs 切片
  • 指针运算
  • 原作者留言
  • 最后


导言

  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.


指针

在这一部分,我们来说说 Go语言 的指针。Go语言 的指针与其他的编程语言,如 CC++,略有不同。

下面就开始讲解吧~



指针是什么?

指针是一个变量,它的值是 另外一个变量的内存地址。

go语言指针数组 go 语言指针_go

在上面的插图中,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)
}

&操作符 可以获取变量的地址。

在上面程序的第 9 行,我们将 b 的地址分配给 a (由于 b 的类型是 int,所以 a 的类型必须为 *int)。此时 a 的值就是 b 的地址,所以我们可以说:a 指向了 b

运行程序,输出如下:

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

在你本机运行时,b 的地址可能不太一样,这是因为:在内存中,存储 b 的位置不止 1 个 。



指针的零值

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

在上面程序的第 8 行,我们使用 new函数,创建了 1 个类型为 *int 的指针 (该指针指向的内存已被清零)。因为 int类型 的零值是 0,所以 size 的类型为 *int*size 等于 0

*size 会在下面讲解。

程序输出如下:

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



指针解引用

指针解引用的意思是:获取该指针指向的变量值。*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,我们解除了 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)
}

在上面程序的第 12 行,我们将 a 指向的值自增 1 。因为 a 指向了 b,所以 b 的值也会自增 1

程序输出为:

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

在上面程序的第 14 行,我们将 指针变量b — 它持有 a 的地址,传递给 函数change。在 change函数 内部,val指针 指向的值被改为 55

程序输出如下:

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



从函数中返回指针

Go 中,我们可以返回 函数内局部变量的地址。因为 Go 的编译器很智能,它能经过逃逸分析,将局部变量分配到堆。

package main

import (  
    "fmt"
)

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

在上面程序的第 9 行,我们返回了 局部变量i 的地址。CC++ 中,执行这样的操作将会导致错误发生,因为函数返回时,栈空间会被回收。而在 Go 中,编译器会经过逃逸分析,将 i 分配到堆上。

因此,程序正常工作,并输出:

Value of d 5



数组指针 vs 切片

假如现在我们想对数组做一些修改,这些修改需要在函数内执行,且必须在函数外可见。

其中的一个实现方法就是:将 指向数组的指针 传递给函数。

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 行,我们解除 arr 的引用,并将被指向数组的第 1 个元素修改为 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函数 传递了 1 个切片。modify函数 会将切片的第1个元素,修改为 90

程序同样会输出:

[90 90 91]

所以,请不要再使用数组指针了,使用切片代替吧!这会使代码更简洁。



指针运算

虽然 CC++ 支持指针运算,但是 Go 并不支持。

package main

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

上面的程序会抛出编译错误:

main.go:6: invalid operation: p++ (non-numeric type *[3]int)