Go中普通变量存值,指针变量存地址;&取地址,*解引用;指针零值为nil,禁止算术运算和类型强转,传指针可修改原值且避免大对象拷贝。

Go 里普通变量和指针变量不是“两种写法不同但效果一样”的东西,而是根本不同的内存访问路径:普通变量直接读写值,指针变量读写的是地址——而地址本身只是个整数(8 字节),它指向的才是真实数据。
普通变量存值,指针变量存地址
声明 a := 42 时,a 就是那个整数 42,存在栈上;而 p := &a 中的 p 存的不是 42,是类似 0xc00001a240 这样的内存编号。你可以把 p 理解成一张便签纸,上面只写着“去 905 房间拿东西”,它自己不存东西。
常见错误现象:
- 误以为
p = &a后修改p会影响a——其实p只是个地址容器,p = &b只是换了个房间号,跟a毫无关系 - 对未初始化的指针
var p *int直接用*p,触发panic: invalid memory address or nil pointer dereference
取地址 & 和解引用 * 是唯一操作入口
Go 不允许指针算术(比如 p++ 或 p + 1),也不支持类型强制转换,所有指针操作被严格限制在两个符号内:& 获取变量地址,* 根据地址读/写目标值。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
使用场景与要点:
-
&a只能作用于可寻址的变量(不能对字面量、函数调用结果、map 元素等取地址) -
*p前必须确保p != nil,尤其在结构体字段、函数返回值、JSON 解码后对象中容易忽略 -
*p = 100等价于 “把 100 写进p所记的那个内存地址里”,它改的是原变量,不是指针本身
传参时值传递 vs 指针传递的本质差异
Go 所有参数都是值传递,但“值”的内容决定了行为:传 int 是复制 8 字节数字;传 *int 是复制 8 字节地址——后者让函数能触达原始内存位置。
性能与适用性判断:
- 小类型(
int、bool、struct{ x, y int })传值更高效,避免间接寻址开销 - 大结构体或含
[]byte、map、chan的类型,传指针避免拷贝,且是修改原值的唯一方式 - 方法接收者为
*T时,只有*T类型变量能直接调用;T类型变量调用会隐式转成(&t).Method(),但若t是字面量或临时值,编译器可能报错
nil 检查和生命周期是高频踩坑点
指针默认零值是 nil,不像普通变量有确定初值(如 int 是 0)。很多 bug 出在没检查就解引用,尤其是从 map 查值、channel 接收、或函数返回指针时。
容易被忽略的细节:
-
json.Unmarshal([]byte(`{"name":"x"}`), &s)必须传&s,否则解码写入的是s的副本,原变量不变 - 闭包中捕获循环变量
i并起 goroutine,如果误用&i,所有 goroutine 实际共享同一个地址,最后全打印最终值 - 函数返回局部变量地址(如
return &x)是安全的——Go 编译器会自动逃逸分析并分配到堆,但返回局部变量的指针再被长期持有,需确认其生命周期是否覆盖使用方

















