指针
在这篇教程中,我们将学习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指针的有关说明,祝您愉快!