指针类型
# 1. 指针类型
# 1.1 指针类型的定义
如果拥有一个类型 T,那么以 T 作为基类型的指针类型为 *T。
指针类型是依托某一个类型而存在的,比如:一个整型为 int,那么它对应的整型指针就是 *int,也就是在 int 的前面加上一个星号。没有 int 类型,就不会有 *int 类型。而 int 也被称为 *int 指针类型的基类型。
声明一个指针类型变量的语法与非指针类型的普通变量是一样的,以声明一个 *T 指针类型的变量为例:
var p *T
不过 Go 中也有一种指针类型是例外,它不需要基类型,它就是 unsafe.Pointer。unsafe.Pointer 类似于 C 语言中的 void*,用于表示一个通用指针类型,也就是任何指针类型都可以显式转换为一个 unsafe.Pointer,而 unsafe.Pointer 也可以显式转换为任意指针类型,如下面代码所示:
var p *T var p1 = unsafe.Pointer(p) // 任意指针类型显式转换为unsafe.Pointer p = (*T)(p1) // unsafe.Pointer也可以显式转换为任意指针类型
1
2
3unsafe.Pointer 是 Go 语言的高级特性,在 Go 运行时与 Go 标准库中 unsafe.Pointer 都有着广泛的应用。
如果指针类型变量没有被显式赋予初值,那么它的值为 nil:
var p *T
println(p == nil) // true
2
给一个指针类型变量赋值:
var a int = 13
var p *int = &a // 给整型指针变量p赋初值
2
用&a作为 *int 指针类型变量 p 的初值,这里变量 a 前面的&符号称为取地址符号,这一行的含义就是将变量 a 的地址赋值给指针变量 p。
# 1.2 指针类型变量的内存单元
非指针类型变量的内存单元,以最简单的整型变量为例:
对于非指针类型变量,Go 在对应的内存单元中放置的就是该变量的值。对这些变量进行修改操作的结果,也会直接体现在这个内存单元上,如下图所示:
指针类型变量的内存单元,以 *int 类型指针变量为例:
Go 为指针变量 p 分配的内存单元中存储的是整型变量 a 对应的内存单元的地址。也正是由于指针类型变量存储的是内存单元的地址,指针类型变量的大小与其基类型大小无关,而是和系统地址的表示长度有关。
package main
import "unsafe"
type foo struct {
id string
age int8
addr string
}
func main() {
var p1 *int
var p2 *bool
var p3 *byte
var p4 *[20]int
var p5 *foo
var p6 unsafe.Pointer
println(unsafe.Sizeof(p1)) // 8
println(unsafe.Sizeof(p2)) // 8
println(unsafe.Sizeof(p3)) // 8
println(unsafe.Sizeof(p4)) // 8
println(unsafe.Sizeof(p5)) // 8
println(unsafe.Sizeof(p6)) // 8
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
通过 unsafe.Sizeof 函数来计算每一个指针类型的大小,无论指针的基类型是什么,不同类型的指针类型的大小在同一个平台上是一致的。在 x86-64 平台上,地址的长度都是 8 个字节。
unsafe 包的 Sizeof 函数原型如下:
func Sizeof(x ArbitraryType) uintptr
这个函数的返回值类型是 uintptr,这是一个 Go 预定义的标识符。通过 go doc 可以查到这一类型代表的含义:uintptr 是一个整数类型,它的大小足以容纳任何指针的比特模式(bit pattern)。可以将这句话理解为:在 Go 语言中 uintptr 类型的大小就代表了指针类型的大小。
一旦指针变量得到了正确赋值,也就是指针指向某一个合法类型的变量,就可以通过指针读取或修改其指向的内存单元所代表的基类型变量,比如:
var a int = 17
var p *int = &a
println(*p) // 17
(*p) += 3
println(a) // 20
2
3
4
5
如下图:
通过指针变量读取或修改其指向的内存地址上的变量值,这个操作被称为指针的解引用(dereference)。它的形式就是在指针类型变量的前面加上一个星号。