参考文献
地址与指针
何为内存?
(注意,我们这里提到的内存并不是人们常说的计算机的物理内存,而是虚拟的逻辑内存空间)
简单点说:地址就是可以唯一标识某一点的一个编号,即一个数字!我们都见过尺子,我们统一以毫米为单位,一把长1000毫米的尺子,其范围区间为0~999,而我们可以准确的找到35毫米、256毫米处的位置。同样的道理,内存也如此,也是像尺子一样线性排布,只不过这个范围略大。
在我们最广泛使用的32位操作系统下,是从 0~4,294,967,295
之间,而地址就是这之中的的一个编号而已,习惯上,在计算机里地址我们常常用其对应的十六进制数来表示,比如 0x12ff7c
这样。
在C程序中,每一个定义的变量,在内存中都占有一个内存单元,比如int
类型占4个字节
,char
类型占一个字节
等等,每个字节都在0~4,294,967,295之间都有一个对应的编号,C语言允许在程序中使用变量的地址,并可以通过地址运算符"&"得到变量的地址。
何为指针?
简单的讲,地址就是逻辑内存上的编号,而指针虽然也表示一个编号,也是一个地址。但两者性质却不相同。一个代表了常量,另一个则是变量。就好比内存是一把尺子,而指针就是尺子上面的游标,可以左右移动,他某一个时刻是指向一个地方的,这就是指针变量。
Go语言中的指针不能进行偏移和运算,因此Go语言中的指针操作非常简单,我们只需要记住两个符号:&
(取地址)和*
(根据地址取值)。
指针地址和指针类型
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&
字符放在变量前面对变量进行“取地址”操作。 Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int
、*int64
、*string
等。
取变量指针的语法如下:
ptr := &v // v的类型为T
其中:
- v:代表被取地址的变量,类型为
T
- ptr:用于接收地址的变量,ptr的类型就为
*T
,称做T的指针类型。*代表指针。
举个栗子
package main
import "fmt"
func main() {
a := 666
b := &a // 取a的指针地址
fmt.Printf("%p\n", &a) // a的内存地址 输出:0xc0000b0008
fmt.Printf("%p\n", b) // b的变量值 输出:0xc0000b0008
fmt.Printf("%p\n", &b) // b的内存地址 输出:0xc0000aa018
}
指针取值
package main
import "fmt"
func main() {
a := 666
b := &a // 取a的指针地址
fmt.Printf("%p\n", &a) // a的内存地址 输出:0xc0000b0008
fmt.Printf("%p\n", b) // b的变量值 输出:0xc0000b0008
fmt.Printf("%p\n", &b) // b的内存地址 输出:0xc0000aa018
c := *b // 指针取值(根据指针去内存取值)
fmt.Printf("%p\n", &c) // 输出:0xc000018088
fmt.Printf("type of c:%T\n", c) // 输出:type of c:int
fmt.Printf("value of c:%v\n", c) // 输出:value of c:666
}
总结:
取地址操作符&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
指针传值示例:
package main
import "fmt"
func f1(a int) {
a = 789
}
// * 代表从内存地址取值
func f2(a *int) {
*a = 123 // 将内存地址的值重新赋值
}
func main() {
a := 666
f1(a)
fmt.Println(a) // 输出:666
// 此处传递 a 的内存地址
f2(&a)
fmt.Println(a) // 输出:123
}