Go 入门(05):指针——Go 指针 vs C 指针,没有指针运算
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 5 篇,接 结构体与字段。Go 有指针,但没有指针运算——你不能写p++或p + 1,所以比 C 指针安全得多。本篇讲清楚 Go 指针怎么用。
本文要回答的问题
- Go 指针和 C 指针有什么不同?
- 为什么 Go 没有指针运算?是好事还是坏事?
- 什么时候用指针,什么时候用值?
- nil 指针是什么?Go 的 nil 和 C 的 NULL 一样吗?
一、指针就是地址
指针保存的是一个变量的内存地址:
go
x := 42
p := &x // 取 x 的地址,p 的类型是 *int
fmt.Println(p) // 打印地址,类似 0xc00001a0a0
fmt.Println(*p) // 解引用,打印 42
*p = 100 // 通过指针修改 x
fmt.Println(x) // 100&取地址*解引用(读指针指向的值,或通过指针写值)*int的语法读作"指向 int 的指针"
二、Go 指针 vs C 指针
| 对比项 | C 指针 | Go 指针 |
|---|---|---|
| 指针运算 | p++、p + 1 可以 | 禁止,编译报错 |
| 数据转换 | (int*)p 随意转换 | 禁止,类型安全 |
| 空指针 | NULL(本质是 0) | nil |
| 取地址 | 局部变量可以取地址 | 局部变量可以取地址,且不会悬垂(逃逸机制) |
| 野指针 | 常见,free 后忘了置 NULL | GC 管,不会出现野指针 |
| 数组退化为指针 | 数组名退化为指针,可以 arr[1] | 数组不退化,用切片 |
Go 的核心设计:指针就是地址,不能做算术运算,不能类型转换。所以不会出现 *(p+5) 越界访问,不会出现把一个 int* 强转成 float* 然后读出垃圾。
三、指针做函数参数
两个场景要用指针:
场景 1:修改原变量
go
func zero(x *int) {
*x = 0 // 通过指针修改外面
}
a := 42
zero(&a)
fmt.Println(a) // 0场景 2:避免拷贝大结构体
go
type Big struct {
data [10000]int
}
func process(b *Big) { // 传指针,只拷贝 8 字节,而不是整个数组
// ...
}四、返回指针:局部变量的地址,安全
Go 里返回局部变量的地址是安全的——编译器发现它"逃逸"了,会把它分配到堆上:
go
func newCounter() *int {
n := 0
return &n // 编译器自动把 n 逃逸到堆,安全
}
c := newCounter()
*c = 5这和 C 完全不一样。C 里返回局部变量的地址是未定义行为,因为栈帧弹出后内存就废了。Go 的编译器会做逃逸分析,发现地址逃出了函数,就自动分配到堆上,由 GC 管理。
五、nil 指针
Go 指针的零值是 nil:
go
var p *int // 默认 nil
fmt.Println(p == nil) // true对 nil 指针解引用会 panic:
go
var p *int
fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference所以用指针前要检查是否 nil:
go
if p != nil {
fmt.Println(*p)
}六、指针 vs 值:什么时候用哪个
| 场景 | 用值 | 用指针 |
|---|---|---|
| 小类型(int、bool、struct 小对象) | 值拷贝便宜 | 不需要 |
| 大结构体 | 拷贝成本高 | 传指针 |
| 需要修改原变量 | 值拷贝改不了 | 必须用指针 |
| 共享同一个对象 | 拷贝多个副本 | 指针指向同一个 |
| 方法接收者 | 不修改原结构体 | 需要修改原结构体 |
新手原则:拿不准时就传指针,但别乱用。小结构体传值拷贝一下没事,大结构体(比如几百字节)传指针更高效。
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 对 nil 指针解引用 | panic | 用之前检查 != nil |
| 不必要地用指针 | 多一层间接,代码复杂 | 小类型用值,大类型用指针 |
| 忘记指针是引用 | 传指针的函数改了原值,意外 | 明确是否需要修改 |
| 指针赋值传递 nil | 函数里无法判断是 nil 还是空结构体 | 显式检查 |
相关与延伸
下一篇:方法——值接收者 vs 指针接收者;逃逸分析决定变量放栈还是堆,见 GC 与逃逸分析;Go 指针和 C 指针的区别,对应 C 指针基础。
一句话总结
Go 指针:就是地址,& 取地址,* 解引用;没有指针运算,不能 p++,不能类型转换,比 C 安全;返回局部变量地址安全,编译器自动逃逸到堆;nil 指针解引用会 panic,记得检查;大结构体传指针高效,小结构体传值也可以。