go中指针是理解变量生命周期、函数行为和内存共享的底层关键;不掌握会导致方法修改失败、性能下降、json语义混乱及并发panic。

Go 中指针不是“可选技能”,而是理解变量生命周期、函数行为和内存共享的底层钥匙。不掌握它,你写的 struct 方法可能改不了原值,传大对象时性能掉一半,JSON 序列化字段语义混乱,甚至并发读写时 panic 都不知道从哪来。
为什么 &x 不能对字面量或函数调用结果取地址
Go 要求被取地址的对象必须是「可寻址的」——即它在内存中有稳定位置(如变量、结构体字段、切片元素)。字面量(如 42、"hello")是临时值,没有固定地址;函数返回值也是右值,生命周期只到表达式结束。
常见错误现象:
-
&42编译报错:cannot take the address of 42 -
&fmt.Sprintf("a")同样报错,因为fmt.Sprintf返回的是新分配的字符串,不可寻址
正确做法:
- 先赋值给变量,再取地址:
s := "hello"; p := &s - 若需临时构造并传指针,用
new()或复合字面量:p := &struct{X int}{X: 42}
*T 和 T 是完全不同的类型,不能隐式转换
Go 的类型系统严格区分值类型和指针类型。*int 不是 int 的“增强版”,而是独立类型。这直接影响函数签名、方法接收者、map 键、channel 元素等几乎所有上下文。
典型误用:
-
var x int = 5; var p *int = x→ 编译失败:cannot use x (type int) as type *int - 把
*string当作string传给fmt.Printf("%s", p)→ 类型不匹配
使用场景提醒:
- 定义 map 时:
map[string]*User和map[string]User底层行为不同(前者键值分离,后者复制整个结构体) - 方法接收者为
*T时,只能用T的指针调用该方法,T{}字面量需显式取地址:(&T{}).Method()
解引用前不判 nil 是最常见 panic 来源
*p 操作本身不检查 p 是否为 nil,一旦执行就直接触发 panic: invalid memory address or nil pointer dereference。这不是 bug,是 Go 的明确设计:空指针检查由程序员负责。
容易踩的坑:
- 函数参数声明为
*string,但调用方传nil(合法且常见),函数内直接if len(*s) > 0→ panic - 结构体字段是
*int,JSON 反序列化时字段缺失导致该字段为nil,后续未判空就解引用
安全写法示例:
func processName(name *string) {
if name == nil {
log.Println("name not provided")
return
}
fmt.Println("got name:", *name)
}
注意:判空必须用 == nil,不能用 != "" 或其他值判断——nil 指针和空字符串是两回事。
传指针进函数 ≠ 能任意修改外部变量
Go 始终是值传递。传入函数的是指针变量的副本,它和原始指针指向同一地址,但两个指针变量本身是独立的。你能通过 *p = v 修改底层数据,但无法让调用方的指针变量指向新地址。
关键边界:
- ✅
*p = 999:修改了原始变量的值 - ❌
p = &y:只改变了函数内局部变量p的指向,不影响调用方的指针 - ❌
p = new(int):同上,分配新内存,但原指针不变
性能提示:小类型(int、bool、短 string)传值开销极小,强行传指针反而增加间接寻址成本;只有大结构体、需修改原值、或需表达「可选性」(nil 表示未设置)时才用指针。
真正难的不是语法,而是判断「这个变量该不该用指针」——它取决于你是否需要共享内存地址、是否允许被修改、是否参与 JSON/DB 映射语义、是否在 goroutine 间共享。漏掉其中任一维度,都可能埋下运行时隐患。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











