本来这一篇的标题就是指针。但是我发现有些同学对有些概念比较模糊(虽然不影响你编程),所以还是决定来讲讲。
我默认你学过 c 语言的指针的概念。因此我不会讲解过多的指针的概念。另外,在 go 里关于指针取址(
&
)和解引用(
*
) 操作,和 c 语言是一样的,不再重复说明。
本节的目的是澄清一些概念。如果你学的不错,完全可以跳过~~~(希望你不会错过什么。)
1. 变量和 object
gopl 中对变量是这样解释的:
A variable is a piece of storage containing a value.
变量,可以『指示』一块保存了对应类型值的内存。变量可以有自己的『名字』(言外之意也就是它可以没有名字)。
指示一词在这里的含义是:代表、表示,化身。指示一块内存,意思是说,变量这个玩意儿它将这『一块内存』视为『一物』。
在这里,我们将『保存了值的一块内存』称为 object,而变量可以用来『追踪』、『指示』此 object。
上面这段话有点绕,从侧面来解释一下,object 是唯一的,但是可以有多个变量来指示它,或者说用来表示它。
约定:以后我们说变量 x,是说 x 这个变量名指示(表示)的那一块内存。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iNwczM1YzYwUWM2MjYzgDMzYzXwEzNxETM2IzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
图1 object
变量具有一些特性:
- 可以从变量里提取『值』。
- 可以根据变量找到它指示的那段内存地址。
- 可以通过赋值运算符(
)修改其指示的 object 的值。=
如果具备以上几点特征,我们可以称之其为变量,有一些语言里也会把它称为『左值』,有人把『左值』称为可寻址的值(addressable value)。
而『右值』,仅仅只是值而已,不具备以上三点任何特征。它不能寻址,不能对其应用取址操作,只能出现在赋值符号右侧。
最后再总结一下,只要可以用来指示『一块内存』,都可以称其为变量(左值)。
2. 指针(pointer)
在图 1 中 object 的内存首地址,可以称为指针。这个地址的长度,取决于你的处理器架构。比如 32 位架构,首地址长度就是 4 字节;64 位架构,首地址长度就是 8 字节。
2.1 指针变量
指针也可以保存在内存里,那么指示这种『内存』的变量,就可以称为指针变量。当然,指针不一定非得要保存在内存里,只是说如果指针保存在变量里,用起来会方便。
约定:以后为了方便描述,如果我说有一个指针保存在了变量 p 里,而不再说指针保存在一块内存里,有一个名为 p 的变量指示了这一块内存。
2.2 再回忆变量是什么
如果有一个指针
p
,类型是
*int
,
p
保存的是一块内存的地址,那么表达式
*p
将指示『一块内存』。言外之意就是说,
*p
是变量。它具备第 1 节中描述的三个特征:
- 可以从变量里提取『值』。如
var x = *p
- 可以根据变量找到它指示的那段内存地址。如
&(*p)
- 可以通过赋值运算符(
)修改其指示的 object 的值。如=
*p = 10
因此,可以说
*p
是一个变量,也可以说它是左值。
比如下面一段代码:
package main
import "fmt"
func main() {
var x int = 10
var p *int = &x
fmt.Println(&x)
fmt.Println(*p) // 取值
fmt.Println(&(*p)) // 取地址
*p = 20 // 赋值
上面这段代码,有一块内存,它是一个 object,保存了数据
10
,有两个变量指示了这块内存,一个是变量
x
,另一个是变量
*p
.
这个程序印证了第 1 节中的一段话:
object 是唯一的,但是可以有多个变量来指示它。
2.3 指针变量的零值
在 go 里,指针变量的零值是
nil
.
我们可以使用
if p != nil
来对指针进行测试。
2.4 返回局部变量的指针
在 go 里,允许返回局部变量的指针!
这得益于 go 的垃圾自动回收机制。我们可以在函数外面使用其局部变量的指针,不过这会导致该局部变量被延时释放。
3. new 函数
func new(Type) *Type
在 go 里,使用 new 函数来分配内存,返回
*Type
类型的指针。很像 C++ 里的 new 操作符,但在 go 里,它是函数。
这个函数的参数是 type 而不是 value. 例如:
p := new(int32)
它表示申请一块内存用于存放
int32
类型的数据,并返回该内存的地址。前面我们还学过一个类似的函数,名为
make
,它的第一个参数也是 type 而不是 value.
喜欢学习的同学可能会问这样的函数到底是怎么定义的?很抱歉暂时我们还定义不了这样的函数,像
new
,
make
这样的函数,是由编译器在编译时将其链接到真正的函数上去的。
下面两个函数
newInt
,返回 int 类型变量的地址,在 go 里并没有什么区别:
func newInt() *int{
var dummy int
return
func newInt() *int {
return new(int)
}
4. 总结
- 变量
- object
- 指针
- 指针变量的零值
- 返回局部变量的指针
- new 函数