如果你想知道为什么 Go 的运行效率会那么快?
为什么你的程序老是报 invalid memory address or nil pointer dereference?
那了解指针是一定少不了的!
先干一张百度百科是怎么解释指针的图:
看完后感觉如何?
我记得我第一次听说指针是在接触 C++ 的时候,那简直是噩梦,太抽象了!
所以这篇文章我尽量写得通俗易懂点。
很庆幸的是,在后来发展起来的语言,比如 Java 基本就已经看不见他的身影了,因为指针太难了,非常反人类。
回到正题,什么是指针?
计算机最重要的两个硬件就是 CPU 和内存吧,CPU 负责计算,内存负责临时存储数据。
我们在写代码时,定义的变量在程序运行时,这些变量都被放在了内存里面,那内存是怎么存数据呢?
你可以粗暴的理解他就是一个记事本,如下图:
浅绿色的部分就可以理解是内存,里面各种颜色的块就是里面存的数据。
当然实际情况不可能这么规则,不同长度的数据,分配的块大小和长度都不一样。
查过字典的同学都能明白,当我们要查某个字的时候,会去目录里面看这个字在第几页,这个第几页就可以理解是这个字在这个字典里面的地址。
内存也是这样,我们在内存里面存放的任何数据都有一个地址,便于 CPU 去取数据。
存放这个地址的变量就是指针!
Go 里面并没完全抛弃指针,因为在很多时候有指针的存在,在效率上会高很多。
但是个人感觉 Go 对指针的处理非常恰到好处,先来看一段代码:
package main
import "fmt"
func main() {
a := "hello"
fmt.Println(a) //[1]
fmt.Println(&a) //[2]
}
然后这是输出的结果:
hello
0xc000010230
标记了 [1] 的那行,是打印这个变量的值。
标记了 [2] 的那行,是打印这个变量的指针地址。
可以直接修改指向数据的值!
有时候我需要在一个方法里面修改传进来的变量值,比如下面这段代码:
func main() {
a := "hello"
fmt.Println(a)
update(a)
fmt.Println(a) //[1]
}
func update(in string) {
in = "word"
}
这段代码执行后的结果应该是这样的,直接输出两行 hello:
hello
hello
假如我现在想在 update 这个方法里面修改 a 这个变量的值,我想在当 [1] 的那段代码执行时,打印 word ,该怎么办?
此时就可以使用指针来处理,直接把 a 变量的地址传给方法,然后方法里面直接针对这个地址进行操作。
func main() {
a := "hello"
fmt.Println(a)
update(&a)
fmt.Println(a)
}
func update(in *string) {
*in = "word"
}
这里面关键点就是 *:
放在类型前面就是申明这是一个指针类型,放在变量前面就是通过这个指针取他值。
而在变量前面放 & 这个字符,就是取这个变量的指针。
到目前为止,Go 指针的核心知识点就这么多,剩下的都是围绕这些在开展。
来看下这个代码:
var b *int
*b = 12
如果运行会怎么样?
他会报错!
panic: runtime error: invalid memory address or nil pointer dereference
我们要操作指针变量时,他必须是一个有指向的地址。
这段代码里面,变量 b 他声明了,但是内存里面并未分配内存地址给他,所以你此时去操作就会报空指针!
所以怎么处理呢?
初始化时就要求分配地址就好了,比如这样:
var b *int = new(int)
但是我们一般不这么写,会结合 Go 的类型自动推断来处理,如下:
b := new(int)
在 Go 语言里面对指针是非常克制的,比如他不允许直接指针计算等。
那为什么还要考虑保留指针?
如果不用指针,在传值的时候,就只能采用拷贝的方式,所以在复杂或者大的数据传递时会比较消耗性能。
但是在使用时有几点需要注意的:
1、不要对 channel 这类引用类型使用指针
2、对int、bool这样小的数据没必要使用指针
3、如果需要并发安全,则尽可能的不要使用指针
4、指针最好不要嵌套,也就是一个指针指向另一个指针
如果你还有关于 Go 里面指针的疑惑,欢迎给我留言,一起讨论!