Go指针:从值传递到地址操作
只要你继续往 Go 的结构体、方法和性能优化方向学习,就绕不开指针。很多人一提到指针就紧张,尤其是如果之前学 C 时被复杂用法劝退过,会自然把 Go 的指针也想得很难。但 Go 对指针做了明显收敛:它保留了“通过地址操作值”的能力,同时去掉了很多高风险用法。
所以学习 Go 指针时,最重要的不是先背语法,而是先建立一个核心认识:为什么程序有时候要操作值的地址,而不是值的副本。
一、这一篇主要学什么
这一篇重点解决这些问题:
- 值和地址到底是什么关系;
- Go 为什么默认是值传递;
- 什么时候需要指针;
&和*分别在做什么;- 指针和结构体方法为什么总是联系在一起;
- 使用指针时最容易踩哪些坑。
二、为什么学指针之前一定要先理解值传递
Go 只有值传递。也就是说,一个变量传给函数时,默认传过去的是它当前值的一份副本,而不是把原变量本身“交给对方”。
这会带来一个非常关键的结果:
- 在函数里改副本,不会影响外部原值;
- 如果你希望修改能反映到原对象上,就需要借助地址。
这就是指针出现的根本原因。它不是为了炫技,而是为了让你能明确表达:“我现在想操作的是原对象本身。”
三、地址和值到底是什么关系
你可以把值理解为变量当前存放的数据,把地址理解为这个数据所在位置的标识。
如果你只拿到了值,你能看到内容,但通常只能改这份副本;如果你拿到了地址,并且通过地址访问值,就等于获得了“定位并修改原对象”的能力。
所以指针并不神秘,它本质上只是:一个用来指向某个值所在位置的变量。
四、Go 指针和 C 指针有什么差别
很多人害怕指针,本质上是在害怕 C 里的指针复杂度。Go 的指针明显更克制:
- 支持取地址和解引用;
- 不支持指针运算;
- 没有那种随意地址偏移带来的大量危险用法。
这让 Go 指针更适合作为“表达修改关系和共享对象”的工具,而不是底层内存操控工具。也正因为如此,Go 的指针学习门槛其实没有很多人想象得那么高。
五、& 和 * 到底分别是什么意思
这是指针语法里最基础的一部分。
1. & 取地址
&a 的意思是获取变量 a 的地址。
2. * 解引用
如果一个变量保存的是地址,那么 *p 的意思就是“根据这个地址,拿到它指向的值”。
把这两个动作连起来理解就很清晰:
- 先通过
&拿到某个值的地址; - 再通过
*沿着地址找到原值并操作它。
六、一个最基础的示例
1 | package main |
这个例子非常适合帮助建立第一层直觉:
a原本只是一个普通整数变量;&a取得它的地址;updateAge接收到的是地址;*age = 30修改的是原始变量所在位置的值。
所以最后打印出来的不是旧值,而是被函数真正修改后的结果。
七、为什么指针和结构体总是一起出现
你后面学结构体时,会不断看到指针的身影。原因很简单:结构体通常比基础类型更大,而且很多时候我们希望方法能直接修改结构体内部状态。
如果把整个结构体值复制来复制去,会有两个问题:
- 修改的可能只是副本,不是原对象;
- 大对象频繁复制也可能带来额外成本。
所以在 Go 里,结构体方法经常会使用指针接收者。这样写不仅能表达“我可能会改对象状态”,也更符合很多真实业务对象的使用方式。
八、指针和方法接收者怎么理解
当你看到某个方法定义成指针接收者时,通常意味着它更适合操作原对象,而不是一份临时副本。
这往往有两个主要原因:
- 方法内部要修改结构体字段;
- 不希望每次调用都复制整份结构体。
所以判断一个方法要不要用指针接收者,不能只看语法习惯,更要看它是否需要体现“操作原对象”的语义。
九、Go 指针的底层意义应该理解到什么程度
入门阶段不需要一开始就深入逃逸分析和内存分配细节,但至少要知道:
- 指针让多个位置能够指向同一份底层数据;
- 这能提升表达能力,但也意味着共享修改的风险;
- 并不是用了指针就一定更高效,要结合对象大小、生命周期和代码语义理解。
很多人一学指针就把它和“性能优化”强行绑定,这其实不够准确。Go 中使用指针,首先是语义问题,其次才可能涉及性能影响。
十、nil 指针为什么必须有意识
指针既然代表地址,就有可能“不指向任何有效对象”,也就是 nil。如果对 nil 指针直接解引用,程序就会出错。
这意味着:
- 指针变量不是天然有效的;
- 使用前有时需要判断是否为
nil; - 在结构体组合、接口返回和错误路径里,nil 判断会很常见。
所以学习指针时,除了会取地址和解引用,也要开始建立“空指针风险”的意识。
十一、学习 Go 指针时最容易踩的坑
1. 没先想清楚值传递
如果不知道 Go 默认是值传递,就很难真正理解指针存在的意义。
2. 把指针神秘化
其实指针最核心的问题只有一个:你到底想操作副本,还是原值。
3. 误以为用了指针就一定更快
有时使用指针可以减少复制,但有时也会引入更多共享复杂度,不能一概而论。
4. 忽略 nil 指针问题
只会写 *p,却不考虑 p 是否为空,是非常典型的初学错误。
5. 在方法设计上没有统一语义
同一个结构体的方法,如果有些值接收者、有些指针接收者,且没有清晰理由,代码会变得很混乱。
十二、工程实践里怎么理解指针
在真实开发中,指针通常更多承担这些角色:
- 允许函数修改原对象;
- 避免大对象频繁复制;
- 表达“可能为空”的语义;
- 支撑结构体方法和对象状态变更。
所以你完全可以把 Go 指针看成一种更明确的“对象操作方式”,而不是底层黑魔法。
十三、学习建议
- 先把“值传递为什么改不到外部变量”实验做清楚;
- 再通过整数、结构体两个层次练习指针;
- 学方法时重点观察值接收者和指针接收者差异;
- 每次用指针时都问自己:我是为了修改原对象,还是为了少复制一份值;
- 对 nil 指针始终保持警惕。
总结
Go 的指针没有 C 那么危险,但它仍然是理解结构体、方法和数据修改关系的关键基础。真正值得掌握的,是这些核心认识:
- Go 默认是值传递;
- 指针的核心价值是操作原对象而不是副本;
&用来取地址,*用来沿地址访问原值;- 指针和结构体方法经常一起出现,因为它们都围绕对象状态操作;
- 使用指针时要同时关注语义清晰和 nil 风险。
把这些关键点想明白后,后面继续学结构体方法、接口、并发和框架代码时,你会顺得多。
参考资源: