kevinyan815/gocookbook

Go语言指针的用法和使用限制

kevinyan815 opened this issue · 0 comments

基础知识

指针保存着一个值的内存地址,类型 *T代表指向T 类型值的指针。其零值为nil

&操作符为它的操作数生成一个指针。

i := 42
p = &i

*操作符则会取出指针指向地址的值,这个操作也叫做“解引用”。

fmt.Println(*p) // 通过指针p读取存储的值
*p = 21         // 通过指针p设置p执行的内存地址存储的值

为什么需要指针类型呢?参考这样一个例子:

package main

import "fmt"

func double(x int) { 
   x += x
}

func main() { 
   var a = 3 
   double(a)
    fmt.Println(a) // 3
}

在 double 函数里将 a 翻倍,但是例子中的函数却做不到。为什么?因为 Go 语言的函数传参都是 值传递。double 函数里的 x 只是实参 a 的一个拷贝,在函数内部对 x 的操作不能反馈到实参 a。

如果这时,有一个指针就可以解决问题了。

package main
import "fmt"

func double(x *int) { 
   *x += *x
    x = nil
}

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

很常规的操作,不用多解释。唯一可能有些疑惑的在这一句:

x = nil

这得稍微思考一下,才能得出这一行代码根本不影响的结论。因为参数都是值传递,所以 x 也只是对 &a 的一个拷贝。

*x += *x

这一句把 x 指向的值(也就是 &a 指向的值,即变量 a)变为原来的 2 倍。但是对 x 本身(一个指针)的操作却不会影响外层的 a,所以在double函数内部的 x=nil 不会影响外面。

函数内部指针指向变换的过程

Go语言指针的限制

出于安全方面的考虑,相较于 C 语言指针的灵活,Go语言里指针多了不少限制,不过这也算是 Go 的成功之处:既可以享受指针带来的便利,又避免了指针的危险性。

限制一:指针不能参与运算

来看一个简单的例子:

package main

import "fmt"

func main() {
	a := 5
	p := a
	fmt.Println(p)
	p = &a + 3
}

上面的代码将不能通过编译,会报编译错误:

invalid operation: &a + 3 (mismatched types *int and int)

也就是说 Go 不允许对指针进行数学运算。

限制二:不同类型的指针不允许相互转换。

下面的程序同样也不能编译成功:

package main

func main() {
	var a int = 100
	var f *float64
	f = *float64(&a)
}

限制三:不同类型的指针不能比较,也不能相互赋值

这条限制同上面的限制二,因为指针之间不能做类型转换,所以也没法使用==或者!=进行比较了,同学不同类型的指针变量相互之间不能赋值。比如下面这样,也是会报编译错误。

package main

func main() {
	var a int = 100
	var f *float64
	f = &a
}

Go语言的指针是类型安全的,但它有很多限制,所以 Go 还有非类型安全的指针,这就是 unsafe 包提供的 unsafe.Pointer。在某些情况下,它会使代码更高效,当然,也更危险。 具体用法放到后面 unsafe.Pointer 的模块里细说。