Go 语言指针

取地址、解引用、指针接收者、以及 Go 指针为什么比 C 安全。

Intermediate
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)           // 100
Output
42
0xc000012345
42
100

什么时候需要指针

  1. 要在函数里修改调用者的变量
  2. 结构体很大,不想每次复制
  3. 要表达「可能不存在」 —— nil 指针
  4. 方法需要改接收者状态
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)      // 21
Output
20
21

Go 指针比 C 安全在哪

RestrictionEffect
没有指针运算不能 p++ 越界访问
不能类型转换(*int)(unsafe.Pointer(p)) 需显式 unsafe
有 GC不会出现悬垂指针(栈变量被提升到堆)
不能指向任意地址无法构造 0x1234 这样的指针
Go
// 返回局部变量的地址是安全的
func newInt() *int {
	v := 42
	return &v            // 编译器把 v 分配到堆上(逃逸分析)
}

p := newInt()
fmt.Println(*p)
Output
42

Returning 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).N
Output
p 是 nil
5
Go 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.

Friends & Partners