指针
2022年8月23日大约 6 分钟
指针
Go 保留了指针,在一定程度上保证了性能,同时为了更好的 GC 和安全考虑,又限制了指针的使用。
创建
关于指针有两个常用的操作符,一个是取地址符&
,另一个是解引用符*
。对一个变量进行取地址,会返回对应类型的指针,例如:
func main() {
num := 2
p := &num
fmt.Println(p)
}
指针存储的是变量num
的地址
0xc00001c088
解引用符则有两个用途,第一个是访问指针所指向的元素,也就是解引用,例如
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}
p
是一个指针,对指针类型解引用就能访问到指针所指向的元素。还有一个用途就是声明一个指针,例如:
func main() {
var numPtr *int
fmt.Println(numPtr)
}
<nil>
*int
即代表该变量的类型是一个int
类型的指针,不过指针不能光声明,还得初始化,需要为其分配内存,否则就是一个空指针,无法正常使用。要么使用取地址符将其他变量的地址赋值给该指针,要么就使用内置函数new
手动分配,例如:
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}
更多的是使用短变量
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}
new
函数只有一个参数那就是类型,并返回一个对应类型的指针,函数会为该指针分配内存,并且指针指向对应类型的零值,例如:
func main() {
fmt.Println(*new(string))
fmt.Println(*new(int))
fmt.Println(*new([5]int))
fmt.Println(*new([]float64))
}
0
[0 0 0 0 0]
[]
禁止指针运算
在 Go 中是不支持指针运算的,也就是说指针无法偏移,先来看一段 C++代码:
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = &arr[0];
cout << &arr << endl
<< p << endl
<< p + 1 << endl
<< &arr[1] << endl;
}
0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884
可以看出数组的地址与数字第一个元素的地址一致,并且对指针加一运算后,其指向的元素为数组第二个元素。Go 中的数组也是如此,不过区别在于指针无法偏移,例如
func main() {
arr := [5]int{0, 1, 2, 3, 4}
p := &arr
println(&arr[0])
println(p)
// 试图进行指针运算
p++
fmt.Println(p)
}
这样的程序将无法通过编译,报错如下
main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)
提示
标准库unsafe
提供了许多用于低级编程的操作,其中就包括指针运算,前往标准库-unsafe了解细节。
new 和 make
在前面的几节已经很多次提到过内置函数new
和make
,两者有点类似,但也有不同,下面复习下。
func new(Type) *Type
- 返回值是类型指针
- 接收参数是类型
- 专用于给指针分配内存空间
func make(t Type, size ...IntegerType) Type
- 返回值是值,不是指针
- 接收的第一个参数是类型,不定长参数根据传入类型的不同而不同
- 专用于给切片,映射表,通道分配内存。
下面是一些例子:
new(int) // int指针
new(string) // string指针
new([]int) // 整型切片指针
make([]int, 10, 100) // 长度为10,容量100的整型切片
make(map[string]int, 10) // 容量为10的映射表
make(chan int, 10) // 缓冲区大小为10的通道