Go 里的指针不复杂,但非常实用,不像 C/C++ 那么危险,也不像 Java 那样完全看不到。
一、Go 指针是什么
1. 指针的本质
指针 = 变量的内存地址
Go
var a int = 10
var p *int = &a
-
a:值是10 -
&a:a 的地址 -
p:指向a的指针 -
*p:通过指针访问a的值
Go
fmt.Println(a) // 10
fmt.Println(p) // 0xc00001a0a8(地址)
fmt.Println(*p) // 10
口诀:
& 取地址, 解引用*
2. Go 指针的"安全边界"
和 C/C++ 不同,Go:
-
不能指针运算(
p++不存在) -
不能随便转类型
-
有 GC(不用手动 free)
-
空指针是
nil
Go
var p *int
fmt.Println(p == nil) // true
二、Go 指针使用场景
1. 修改函数外的变量(非常核心)
❌ 值传递(改不到外面):
Go
func add(a int) {
a++
}
func main() {
x := 10
add(x)
fmt.Println(x) // 10
}
✅ 指针传递:
Go
func add(a *int) {
*a++
}
func main() {
x := 10
add(&x)
fmt.Println(x) // 11
}
Go 只有值传递,但"指针的值"可以指向同一块内存
2 结构体 + 指针(Go 的高频用法)
Go
type User struct {
Name string
Age int
}
func grow(u *User) {
u.Age++
}
func main() {
u := User{Name: "Tom", Age: 18}
grow(&u)
fmt.Println(u.Age) // 19
}
注意: Go 一个很贴心的地方:
Go
u.Age++ // 等价于 (*u).Age++
Go 自动帮解引用,不需要满屏 *
3. new / & 的区别
Go
p1 := new(int) // *int,值是 0
p2 := &User{} // *User
等价写法:
Go
var a int
p := &a
一般习惯:
-
基本类型:
& -
结构体:
&User{}或构造函数
三、Go 指针的核心使用场景
场景 1:需要修改对象本身(最常见)
Go
func updateName(u *User) {
u.Name = "Jack"
}
场景 2:避免大对象拷贝(性能 & 内存)
Go
type BigStruct struct {
Data [100000]int
}
func process(b *BigStruct) {
// 不拷贝 100000 个 int
}
场景 3:区分"没传"和"传了零值"
这个在 API / JSON / DB 特别重要
Go
type Req struct {
Age *int `json:"age"`
}
-
nil→ 前端没传 -
0→ 前端明确传了 0
场景 4:方法接收者用指针(Go 面向对象)
Go
func (u *User) Grow() {
u.Age++
}
什么时候用指针接收者?
-
需要修改对象
-
结构体比较大
-
保证方法一致性(推荐)
官方建议:一个结构体,要么全指针接收者,要么全值接收者
场景 5:与 interface 配合
Go
type Writer interface {
Write()
}
type File struct{}
func (f *File) Write() {}
var w Writer
w = &File{} // 正确
这样不行,因为方法在 *File 上:
Go
w = File{} // 没实现接口
场景 6:并发 & 共享状态
需谨慎使用
Go
var count int
var mu sync.Mutex
func inc() {
mu.Lock()
count++
mu.Unlock()
}
虽然不是"显式指针",但底层都是共享内存 + 地址
四、Go 指针 vs Java/C++
| 对比 | Go | Java | C++ |
|---|---|---|---|
| 手动内存 | ❌ | ❌ | ✅ |
| 指针运算 | ❌ | ❌ | ✅ |
| 空指针 | nil |
null |
nullptr |
| 参数传递 | 值传递 | 值传递(引用语义) | 值/引用 |
Go 指针 = "受控版 C 指针 + Java 引用的灵活性"
五、新手常见坑
❌ 对 map / slice 再取指针
Go
func f(m *map[string]int) // 一般没必要
因为:
-
map / slice 本身就是"引用类型"
-
直接传就能改
❌ nil 指针解引用
Go
var u *User
u.Age = 10 // panic
一定要先初始化。
六、总结
Go 指针的目标只有三个:
-
修改原数据
-
减少拷贝
-
表达"可选值"