Golang 从入门到放弃 -0x06
理解指针、传值和传址,搞清 Go 指针和 C 指针的区别与边界。
一提到指针,很多人条件反射就是头皮一紧。尤其是被C语言教育过的同学,往往脑海里已经浮现出野指针、段错误、内存踩踏这些经典节目。先别慌,Go里的指针相对收敛得多,没那么容易把自己玩炸。
指针到底是什么
简单说,指针保存的是另一个变量的地址。你可以把它理解成“这个变量放在哪儿”的门牌号。
package main
import "fmt"
func main() {
num := 10
p := &num
fmt.Println(num)
fmt.Println(p)
fmt.Println(*p)
}这里有两个符号一定要先分清:
&取地址*解引用,也就是顺着地址把值拿出来
p := &num 表示把 num 的地址交给 p。*p 表示找到这个地址里存着的值。
为什么需要指针
最直接的原因,是你想在函数里改掉外面的变量。
func addOne(n int) {
n++
}
func addOneByPointer(n *int) {
*n = *n + 1
}func main() {
num := 10
addOne(num)
fmt.Println(num) // 10
addOneByPointer(&num)
fmt.Println(num) // 11
}第一个函数拿到的是值的拷贝,改的是副本。第二个函数拿到的是地址,改的就是原件。
new 关键字
Go 里还有个 new,它会帮你分配一块对应类型的空间,并返回指针。
p := new(int)
fmt.Println(*p) // 0
*p = 99
fmt.Println(*p)不过说实话,业务开发里 new 的存在感并没有那么强。很多时候你直接声明变量,再取地址,就已经够用了。
指针也会是 nil
如果一个指针没有指向任何对象,它的值就是 nil。这时候你要是还硬去解引用,程序会直接崩给你看。
var p *int
fmt.Println(p == nil) // true
// fmt.Println(*p) // 这行会 panic所以指针相关代码里,判空是个好习惯。
Go 的指针,和 C 不一样
Go 故意把指针这把刀磨钝了一些:
- 不能做指针运算
- 没有随手乱偏移地址的自由
- 配合GC,不需要你手工
free
这意味着你依然能用指针表达“我要改原对象”这件事,但很难像C里那样一路开着碰碰车冲进内存深处。
什么时候该用指针
可以先用一个很朴素的标准判断:
- 需要修改原值,用指针。
- 对象比较大,不想频繁复制,用指针。
- 只是读一下,数据也不大,直接传值就行。
别一上来就万物皆指针。Go 默认传值,是有它自己的美学在的。代码简单的时候,保持简单就很好。
和结构体的关系
后面讲 struct 和方法的时候,你会经常看到指针接收者,那时指针会真正进入高频使用阶段。现在你只需要先有一个感觉:指针就是“我不是复制一份给你,我把原件地址给你”。
小结
这一章记住这些就够了:
&取地址,*解引用。- 传值不会改原对象,传指针才会。
- Go 的指针没那么危险,但
nil指针照样能把你拍地上。
下一章我们把数组、切片和 map 放到一起聊。这里面切片尤其重要,基本属于 Go 日常开发的主食。