区别于 C/C++ 的指针,Go 语言的指针不能进行偏移和运算,因此是安全指针。
指针
一个变量在内存中,可以分为两部分:编址(变量的地址)和具体的值。
在概念上,Go 语言的指针和 C 语言一样,当一个变量的值存储的值是其他变量的地址,那么它就是一个指针变量。
所以也可以说指针的本质就是地址。
操作指针
前面提到过,因为 Go 语言的指针不能进行偏移和运算,因此指针的使用场景就只有传递了,只需要记住两个操作符即可:
&
:取址符,也称为引用,通过该操作符可以获取一个变量的地址值。*
:取值符,也称为解引用,通过该操作符可以获取一个地址对应的值。
指针的类型
每个变量在内存中都有一个属于自己的地址,不同类型的数据,都可以拥有自己的指针,比如:
int
=>*int
,也叫整型指针string
=>*string
,也叫字符串指针struct
=>*struct
,也叫结构体指针- …
无论是什么类型,占用的内存都一样(32位4个字节, 64位8个字节)。
指针在内存中的表示
按照惯例先来看一段示例代码:
1 | func main() { |
执行示例代码,输出以下结果:
1 | x: 10, ptr: 0xc0000b2008, T: int |
在上面的示例代码中,变量 x 通过取址符,获取到自己的编址并赋值给变量 p,因此变量 p 就是一个指针变量,其类型为整型,也可以说变量 p 就是一个整型指针。
因为变量 p 是一个指针变量,所以它也拥有自己的地址,而它的值则是变量 x 的地址。
下面用一张图来解释它们之间的关系:
&
符号的作用是获取变量的地址,*
符号的作用是通过变量的地址获取对应的值。
指针作为函数参数和返回值
指针传递的场景还包括作为函数参数和返回值,下面一一来看下,这两种场景下,都有哪些特点。
作为函数参数
1 | type T struct { |
运行上面的示例代码,会发现编译失败了:
cannot use t1 (variable of type T) as type *T in argument to F
因为函数 F 接收一个指针作为参数,但是传进去的 t1 并不是一个结构体指针,而是一个结构体,类型不一致,所以导致编译失败。
要解决这个问题很简单,只需要保证形参和实参的类型一致即可,使用 &
取址符将 t1 的地址作为参数传入:
1 | F(&t1) |
再次运行示例代码:
1 | 0 |
总结:
- 形参与实参的类型必须一致,否则会编译失败
- 对形参进行任何修改都会影响到实参
作为返回值
一个问题
在方法的 receiver 参数这篇笔记中,当时遇到了一个 Go编译器做指针自动转换的场景,有了上面的基础做铺垫就不难理解了。
1 | type T struct { |
t1 作为一个结构体(非指针),理论上是不能直接调用 receiver 参数类型 *T
(指针)的 M2,Go 编译器在背后做了自动转换,使用 &
取址符将t1 变成了结构体指针,也就是 (&t1).M2()
。
同理,t2 作为一个结构体指针(指针),理论上是不能直接调用 receiver 参数类型 T
(非指针)的 M1,同样是Go 编译器在背后做了自动转换,使用 *
取值符将t2 变成了结构体,也就是 (*t2).M1()
。