指针
一 指针介绍
1.1 什么是指针
我们将内存中字节单元的编号称为:地址(Address)
如果把内存单元比喻为小柜子,那么内单元的地址就相当于柜子的编号,如下所示
地址(Address)是用来帮我们找到存储在内存中的数据的,每个地址都指向一块存储空间,可以说“指向”二字很好地表达出了地址的精髓,为了能够更好地反映出地址的精髓,在高级语言中地址也被形象地称为:指针(Pointer)
1.2 如何获取指针
指针就是地址,获取指针就是获取地址,获取变量的指针就是获取变量的地址(首地址)。
对于一个变量
我们可以用&符拿到变量的地址,或者说拿到了一个指向变量的指针
1.3 通过指针可以操作变量值
若想通过指针而非变量名去访问变量,需要在指针前加*号,如下
毫无疑问,我们通过指针与变量名操作的都是同一地址的数据
1.4 指针的类型
其实,指针是有类型的,
但令人疑惑的是:指针就是地址,地址不就是一串十六进制表示的数字么(其实底层都是二进制),哪来的什么类型一说呢?
1.5 指针变量
我们可以将指针/地址保存到一个变量中,这个变量称之为指针变量,指针变量里存放的是另外一个变量的地址
一句话:指针就是地址, 指针变量就是存储地址的变量,变量名x与*p操作的都是同一个空间
二 指针的应用
2.1 储备知识:栈帧的内存布局
程序的内存布局图如下
我们的需要关注的是用户区,针对如下代码
上述代码都存储在用户区的stack(栈)区. 一般go程序调用 make()
或者 new()
出来的都存储在用户区的heap(堆)区
接下来, 我们来了解一个新的概念: 栈帧.
栈帧: 用来给函数运行提供内存空间, 取内存于stack(栈)区上.
当函数调用时, 产生栈帧; 函数调用结束, 释放栈帧.
那么栈帧用来存放什么?
- 局部变量
- 形参
- 内存字段描述值
其中, 形参与局部变量存储地位等同
当我们的程序运行时, 首先运行 main()
, 这时就产生了一个栈帧.
当运行到 var x int = 100
时, 就会在栈帧里面产生一个空间.
同理, 运行到 var p *int = &x
时也会在栈帧里产生一个空间.
如下图所示
我们增加一个函数, 再来研究一下.
如下图所示, 当运行到 foo(111)
时, 会继续产生一个栈帧, 这时 main()
产生的栈帧还没有结束.
当 foo(111)
运行完毕时, 就会释放掉这个栈帧.
2.2 内置函数new(T)
野指针:初始值为无效的内存地址
空指针:初始值为nil的指针
我们可以省略初始化表达式,使用零值初始化机制声明一个指针变量。注意:任何类型的指针的零值都是nil
此时,p的初始值为
我们是无法进行如下赋值的
我们当然可以在声明好指针变量p后,为其赋值一个地址,然后再进行上述赋值操作
但你们发现我们始终都绕不过一个变量名x,如果我们想不依赖于变量x,直接声明一个初始的零值不为nil的指针,那么指针变量的声明语句就不能省略初始化表达式了,必须使用内置函数new(T)作为初始化表达式
此时new()得到的指针变量p就可以赋值啦