hello云胜

技术与生活

0%

指针

go被划为C语言家族的一员,一个重要的原因就是Go和C一样,也支持指针。 当然Go中的指针不是完全体的指针,相比C指针有很多限制。

指针的形式和C一样,*T。T称为基类型。

如果一个指针类型的基类型为T,则此指针类型的值只能存储类型为T的值的地址。

获取一个指针值的方式有两种:

  1. 使用内置函数new。new函数可以为任何类型的值在内存中开辟一块内存并将该内存块的起始地址返回。
  2. 使用取地址符&。&取地址符用来获取一个可寻址值的内存地址。

上面提到了可寻址值,所有变量是可寻址的。但是:常量、函数返回值、强制类型转换的结果是不可寻址的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

func TestPointer(t *testing.T) {
p0 := new(int) // p0指向一个int类型的零值
fmt.Println(p0) // (打印出一个十六进制形式的地址)
fmt.Println(*p0) // *取值

x := *p0 // x是p0所引用的值的一个复制。
p1, p2 := &x, &x // p1和p2现在都存储着x的地址。
// x、*p1和*p2表示着同一个int值。
fmt.Println(p1 == p2) // true
fmt.Println(p0 == p1) // false
p3 := &*p0 // 对p0取值再取地址,等价于p3 := p0
fmt.Println(p0 == p3) // true
*p0, *p1 = 123, 789 // p0的值改为123,p0和p3是同一个地址。p1,p2和x是同一个地址
fmt.Println(*p2, x, *p3) // 789 789 123
}

在Go中,所有的赋值(包括函数调用传参)过程都是一个值复制过程。

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

import "fmt"

func double(x *int) {
*x += *x // 取出地址对应的值进行操作
x = nil // 操作的是p地址的副本
}

func main() {
var a = 3
double(&a)
fmt.Println(a) // 6
p := &a
double(p)
fmt.Println(a, p == nil) // 12 false
}

将a的地址传入,操作的是地址指向的值。所以函数内的操作反应到了函数外。

而x = nil,此时的x是传入的p的地址值的一个副本。修改x并不会影响p的值

go指针的限制

  • go指针不支持算术运算。

    这样更安全,开发者不能随意修改内存

  • 一个指针类型的值不能随意转为为另一个指针类型。只有两个基类型的底层数据类型一致才能转换。

  • 使用unsafe包可以打破上述限制

java中没有指针。硬要类比,java的引用类型可以放一起看看。