指针
Q7nl1s admin

在本教程中,我们将学习指针在 Go 中是如何工作的,我们还将了解 Go 指针与其他语言(如 C 和 C++)中的指针有何不同。

什么是指针?

指针是存储另一个变量的内存地址的变量。

Go 中的指针

在上图中,变量b的值为156并存储在内存地址0x1040a124中。变量a保存b的地址。现在说a指向b

声明指针

*T 是指向 T 类型值的指针变量的类型

让我们编写一个声明指针的程序。

1
2
3
4
5
6
7
8
9
10
11
12
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。现在说a指向b。当我们打印a中的值时,将打印b的地址。该程序输出:

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

您可能会得到不同的b的地址,因为 b 的位置可以在内存中的任何位置。

指针的零值

指针的零值为nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 的地址。该程序输出:

1
2
b is <nil>  
b after initialisation is 0x1040a124

使用新函数创建指针

Go 还提供了一个方便的函数new来创建指针。该new函数将一个类型作为参数并返回一个指针,该指针指向作为参数传递的类型的新分配的零值。

下面的例子会让事情变得更清楚。

1
2
3
4
5
6
7
8
9
10
11
12
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函数来创建int类型的指针。该函数将返回一个指向新分配的int类型的指向该类型零值的指针。int类型的零值为0. 因此 size 将是 *int类型并将指向0,换句话说 *size将会是 0

上面的程序将打印:

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

取消引用指针

取消引用指针意味着访问指针指向的变量的值。*a是服从 a 的语法。

让我们看看这在程序中是如何工作的。

1
2
3
4
5
6
7
8
9
10
11
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 的值。程序的输出是:

1
2
address of b is 0x1040a124  
value of b is 255

让我们再编写一个程序,在其中使用指针更改b中的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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指向的值递增,这会改变 b 的值,因为 a 指向 b。因此 b 的值变为 256。程序的输出是:

1
2
3
address of b is 0x1040a124  
value of b is 255
new value of b is 256

将指针传递给函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 行,我们将保存 a 地址的指针变量 b 传递给change函数。在change函数内部,通过第 8 行中的取消引用来更改 a 的值。该程序输出:

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

从函数返回指针

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)

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

上面程序的第 9 行,我们从hello函数中返回局部变量i的地址。此代码的行为在 C 和 C++ 等编程语言中是未定义的,因为一旦hello函数返回局部变量的指针,变量i就会超出它的作用域。但是在 Go 中,编译器会进行转义分析,当i脱离本地作用域时在堆上为其分配地址。因此,该程序将生效用并打印:

1
Value of d 5  

不要将指向数组的指针作为参数传递给函数,改用切片。

假设我们要对函数内部的数组进行一些修改,并且对函数内部的数组所做的更改应该对调用者可见。一种方法是将指向数组的指针作为参数传递给函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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函数。在modify函数的第 8 行内,我们取消引用 arr 并分配90给数组的第一个元素。该程序输出[90 90 91]

\a[x] 是 (*a)[x] 的简写。所以上述程序中的 (*arr)[0] 可以替换为 arr[0]。让我们用这个速记语法重写上面的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 中实现这一点的惯用方式。我们有切片来专门实现这方面的操作。

让我们使用slices重写相同的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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函数。切片的第一个元素在modify函数内部更改为90。该程序还输出[90 90 91]所以不要传递指向数组的指针,而是使用切片:)。这段代码更简洁,并且在 Go 中是惯用的 :)。

Go不支持指针运算

Go 不支持其他语言(如 C 和 C++)中存在的指针算法。

1
2
3
4
5
6
7
package main

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

上述程序会抛出编译错误main.go:6: invalid operation: p++ (non-numeric type *[3]int)

这就是 Go 中的指针。祝你有美好的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View