Go 语言指针
取地址、解引用、指针接收者、以及 Go 指针为什么比 C 安全。
Key Points`&` 取地址,`*` 解引用;Go 没有指针运算,也不能指针类型转换。
基本操作
Go
x := 42
p := &x // p 是 *int
fmt.Println(x) // 42
fmt.Println(p) // 0xc000012345
fmt.Println(*p) // 42
*p = 100 // 通过指针改值
fmt.Println(x) // 100Output
42
0xc000012345
42
100什么时候需要指针
- 要在函数里修改调用者的变量
- 结构体很大,不想每次复制
- 要表达「可能不存在」 ——
nil指针 - 方法需要改接收者状态
Go
type User struct {
Name string
Age int
}
// 值接收者:改不了
func (u User) birthdayWrong() { u.Age++ }
// 指针接收者:能改
func (u *User) birthday() { u.Age++ }
u := User{Name: "张三", Age: 20}
u.birthdayWrong()
fmt.Println(u.Age) // 20
u.birthday()
fmt.Println(u.Age) // 21Output
20
21Go 指针比 C 安全在哪
| Restriction | Effect |
|---|---|
| 没有指针运算 | 不能 p++ 越界访问 |
| 不能类型转换 | (*int)(unsafe.Pointer(p)) 需显式 unsafe |
| 有 GC | 不会出现悬垂指针(栈变量被提升到堆) |
| 不能指向任意地址 | 无法构造 0x1234 这样的指针 |
Go
// 返回局部变量的地址是安全的
func newInt() *int {
v := 42
return &v // 编译器把 v 分配到堆上(逃逸分析)
}
p := newInt()
fmt.Println(*p)Output
42Returning a local's address is undefined behaviour in C but perfectly legal in Go: escape analysis moves variables that may outlive the function to the heap, where the GC manages them.
nil 指针
Go
var p *int
// fmt.Println(*p) // panic: nil pointer dereference
if p != nil {
fmt.Println(*p)
} else {
fmt.Println("p 是 nil")
}
// 结构体指针的字段访问会自动解引用
type T struct{ N int }
var t *T
// fmt.Println(t.N) // panic
t = &T{N: 5}
fmt.Println(t.N) // 等价于 (*t).NOutput
p 是 nil
5Go rewrites
p.Field as (*p).Field, so it reads like a value and it is easy to forget p may be nil. Check the receiver for nil first if it can be called on nil.new 与 &T{}
Go
a := new(int) // *int,指向零值
fmt.Println(*a) // 0
b := &User{} // 更常用,能直接初始化字段
c := &User{Name: "李四"}
fmt.Println(b.Name == "", c.Name)Output
0
true 李四new(T) only allocates a zero value and is almost always replaceable by the more readable &T{}. new is rare in real Go code.
基本操作
Go
x := 42
p := &x // p 是 *int
fmt.Println(x) // 42
fmt.Println(p) // 0xc000012345
fmt.Println(*p) // 42
*p = 100 // 通过指针改值
fmt.Println(x) // 100Output
42
0xc000012345
42
100什么时候需要指针
- 要在函数里修改调用者的变量
- 结构体很大,不想每次复制
- 要表达「可能不存在」 ——
nil指针 - 方法需要改接收者状态
Go
type User struct {
Name string
Age int
}
// 值接收者:改不了
func (u User) birthdayWrong() { u.Age++ }
// 指针接收者:能改
func (u *User) birthday() { u.Age++ }
u := User{Name: "张三", Age: 20}
u.birthdayWrong()
fmt.Println(u.Age) // 20
u.birthday()
fmt.Println(u.Age) // 21Output
20
21Go 指针比 C 安全在哪
| 限制 | 效果 |
|---|---|
| 没有指针运算 | 不能 p++ 越界访问 |
| 不能类型转换 | (*int)(unsafe.Pointer(p)) 需显式 unsafe |
| 有 GC | 不会出现悬垂指针(栈变量被提升到堆) |
| 不能指向任意地址 | 无法构造 0x1234 这样的指针 |
Go
// 返回局部变量的地址是安全的
func newInt() *int {
v := 42
return &v // 编译器把 v 分配到堆上(逃逸分析)
}
p := newInt()
fmt.Println(*p)Output
42C 里返回局部变量地址是未定义行为,Go 里完全合法——逃逸分析会把可能逃出函数的变量分配到堆上,由 GC 管理。
nil 指针
Go
var p *int
// fmt.Println(*p) // panic: nil pointer dereference
if p != nil {
fmt.Println(*p)
} else {
fmt.Println("p 是 nil")
}
// 结构体指针的字段访问会自动解引用
type T struct{ N int }
var t *T
// fmt.Println(t.N) // panic
t = &T{N: 5}
fmt.Println(t.N) // 等价于 (*t).NOutput
p 是 nil
5Go 里
p.Field 会自动写成 (*p).Field,读起来像值一样,很容易忘了 p 可能是 nil。方法里第一件事就是检查接收者是否为 nil(如果它可能被 nil 调用)。new 与 &T{}
Go
a := new(int) // *int,指向零值
fmt.Println(*a) // 0
b := &User{} // 更常用,能直接初始化字段
c := &User{Name: "李四"}
fmt.Println(b.Name == "", c.Name)Output
0
true 李四new(T) 只分配零值,几乎总能用 &T{} 替代且更可读。Go 代码里 new 的出现频率极低。