学Go语言的你,是不是一碰到“指针”就头大?
看着代码里的&和*,一会儿取地址一会儿解引用,总觉得抽象又绕,甚至怕写错不敢用——明明知道指针很重要,却卡在“这俩符号到底啥意思”上。
其实真不用怕!指针的核心就是“记地址”和“找东西”,而&和*就是干这两件事的工具。今天用生活例子+极简代码,帮你10分钟搞懂它们,以后写指针再也不慌!
先搞懂:指针到底是啥?用生活例子说透
别被“指针”这俩字吓住,它其实很具象——就像你生活里记门牌号的纸条。
举个例子:
• 你有个变量a,值是10,它存在电脑内存里的某个“房间”里,这个房间有个唯一的“门牌号”(比如0xc00001a0a8,这就是内存地址);
• 指针变量p,它不存10这种具体值,而是存a的“门牌号”(0xc00001a0a8);
• 有了p,你就知道a住在哪个“房间”,能随时找到它、甚至修改它——这就是指针的作用:通过地址间接操作变量。
而&和*,就是帮你“获取门牌号”和“按门牌号找东西”的两个工具,分工特别明确。
第一个符号:&(取地址符)——“抄门牌号”
&的作用只有一个:获取变量的内存地址(也就是抄下变量的“门牌号”)。
生活类比
你朋友住在“幸福路123号”(变量a的内存地址),你想记下来,就拿出纸条抄下这个地址——&a就是“抄门牌号”的动作。
极简代码示例(可直接复制运行)
package main
import "fmt"
func main() {
// 1. 定义普通变量a,值是10(相当于“朋友住在某个房间,房间里有东西10”)
a := 10
// 2. 打印a的值(看房间里的东西)
fmt.Println("变量a的值:", a) // 输出:变量a的值:10
// 3. 用&取a的内存地址(抄门牌号)
fmt.Println("变量a的内存地址(门牌号):", &a) // 输出类似:变量a的内存地址(门牌号):0xc00001a0a8
}关键结论
•&必须跟在“变量”后面,比如&a、&b,不能跟数字(比如&10会报错);
• 每次运行程序,内存地址可能不一样(就像每次换房子门牌号变了),但不影响使用;
• 用&得到的“地址”,可以赋值给一个“指针变量”(就像把抄好的门牌号贴在小本子上)。
第二个符号:*(两种用法,别搞混!)
*比&稍微复杂一点,因为它有两个完全不同的作用——但只要结合场景,一眼就能分清。
用法1:声明指针类型——“声明‘记门牌号的小本子’”
当*跟在“数据类型”前面时,作用是声明一个“指针变量”(也就是告诉Go:“我要一个小本子,专门记某类变量的门牌号”)。
生活类比
你说“我要一个专门记‘居民楼房间号’的小本子”——var p *int就是“我要一个专门记int类型变量地址的指针变量”。
代码示例
package main
import "fmt"
func main() {
a := 10
// 1. 用*int声明指针变量p(“小本子p,专门记int类型的门牌号”)
// 刚声明时p是空的(nil),就像小本子还没写门牌号
var p *int
// 2. 把a的地址(&a)赋值给p(在小本子上写下a的门牌号)
p = &a
// 3. 打印p的值(看小本子上的门牌号)
fmt.Println("指针p存储的地址:", p) // 输出和&a一样,比如:0xc00001a0a8
// 打印&a验证
fmt.Println("变量a的地址(&a):", &a) // 输出和p相同,说明p确实存了a的地址
}用法2:指针解引用——“按门牌号找房间里的东西”
当*跟在“指针变量”前面时,作用是获取指针指向的变量的值(也就是按小本子上的门牌号,找到房间里的东西),这个动作叫“解引用”。
更厉害的是:你还能通过*指针变量修改房间里的东西!
生活类比
你看小本子上的门牌号“幸福路123号”,去这个房间把里面的“10”换成“20”——*p = 20就是这个动作。
代码示例(核心用法,必看!)
package main
import "fmt"
func main() {
a := 10
// 1. 让指针p指向a(小本子记下a的门牌号)
p := &a // 这里是“&取地址”+“指针变量赋值”,一步到位
// 2. 解引用:用*p获取a的值(按门牌号找东西)
fmt.Println("通过*p获取a的值:", *p) // 输出:通过*p获取a的值:10
// 3. 解引用:用*p修改a的值(按门牌号改东西)
*p = 20 // 相当于“去a的房间,把10改成20”
// 4. 打印a的值,看是否被修改
fmt.Println("修改后a的值:", a) // 输出:修改后a的值:20
}关键结论
• 记准场景:*在“类型前”是声明指针(比如*int),在“指针变量前”是解引用(比如*p);
• 解引用的核心:通过指针“间接操作”目标变量,不用直接碰a,也能改a的值。
新手必看:指针的核心使用场景(函数参数传递)
光懂符号还不够,得知道指针在实际代码里怎么用——最常见的场景就是“函数参数传递”。
Go语言默认是“值传递”:给函数传变量时,传的是“变量的副本”,函数里改副本,外面的原变量不变。如果想在函数里改外面的变量,就得传指针(传变量的地址)。
对比示例:值传递 vs 指针传递
package main
import "fmt"
// 场景1:值传递(参数是普通int,传的是副本)
func modify1(x int) {
x = 100 // 只改了副本,原变量a不变
fmt.Println("函数里modify1的x值:", x) // 输出:函数里modify1的x值:100
}
// 场景2:指针传递(参数是*int,传的是地址)
func modify2(x *int) {
*x = 100 // 解引用,改的是指针指向的原变量a
fmt.Println("函数里modify2的*x值:", *x) // 输出:函数里modify2的*x值:100
}
func main() {
a := 10
// 1. 调用值传递的函数
modify1(a)
fmt.Println("调用modify1后,原变量a的值:", a) // 输出:10(没被改)
fmt.Println("------------------------")
// 2. 调用指针传递的函数(传a的地址&a)
modify2(&a)
fmt.Println("调用modify2后,原变量a的值:", a) // 输出:100(被改了)
}新手提问:什么时候用指针传递?
• 想在函数里修改外部变量的值(比如上面的modify2);
• 变量很大(比如大数组、结构体),传副本浪费内存,传地址更高效(地址就8个字节,不管变量多大)。
避坑指南:新手最容易犯的3个错
1.空指针解引用(必报错)
声明了指针变量但没赋值(比如var p *int),就直接用*p,会报错——因为小本子上没门牌号,找不到房间。
解决:先让指针指向一个变量(比如p = &a),再解引用。
2.把*的两种用法搞混
比如写var p int*(错),正确是var p *int——*要跟在类型前面,不是后面。
3.给指针传错类型
比如var p *int,却给p赋值&"hello"(错)——p是记int地址的小本子,不能记字符串的地址。
一句话总结:&和*到底怎么用?
•&变量:抄变量的门牌号(取地址),返回一个指针;
•*类型:声明一个“记该类型门牌号的小本子”(指针变量);
•*指针变量:按小本子上的门牌号找东西(解引用),能读能改。
最后问一句:你之前学指针时,最搞不懂的是&还是*?评论区聊聊,有问题我帮你解答!