Skip to content

Go指针 ​

阅读提示

  • 适用版本:Go 1.21+(示例在 Go 1.22 下验证)· 前置知识:变量 · 阅读时长:约 10 分钟

Go 保留了指针,在一定程度上保证了性能,同时为了更好的 GC 和安全考虑,又限制了指针的使用。

创建 ​

关于指针有两个常用的操作符,一个是取地址符&,另一个是解引用符*。对一个变量进行取地址,会返回对应类型的指针,例如:

go
func main() {
   num := 2
   p := &num
   fmt.Println(p)
}

指针存储的是变量num的地址

0xc00001c088

解引用符则有两个用途,第一个是访问指针所指向的元素,也就是解引用,例如

go
func main() {
  num := 2
  p := &num
  rawNum := *p
  fmt.Println(rawNum)
}

p是一个指针,对指针类型解引用就能访问到指针所指向的元素。还有一个用途就是声明一个指针,例如:

go
func main() {
   var numPtr *int
   fmt.Println(numPtr)
}
<nil>

*int即代表该变量的类型是一个int类型的指针,不过指针不能光声明,还得初始化,需要为其分配内存,否则就是一个空指针,无法正常使用。要么使用取地址符将其他变量的地址赋值给该指针,要么就使用内置函数new手动分配,例如:

go
func main() {
   var numPtr *int
   numPtr = new(int)
   fmt.Println(numPtr)
}

更多的是使用短变量

go
func main() {
   numPtr := new(int)
   fmt.Println(numPtr)
}

new函数只有一个参数那就是类型,并返回一个对应类型的指针,函数会为该指针分配内存,并且指针指向对应类型的零值,例如:

go
func main() {
   fmt.Println(*new(string))
   fmt.Println(*new(int))
   fmt.Println(*new([5]int))
   fmt.Println(*new([]float64))
}

0
[0 0 0 0 0]
[]

禁止指针运算 ​

在 Go 中是不支持指针运算的,也就是说指针无法偏移,先来看一段 C++代码:

cpp
int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *p = &arr[0];
    cout << &arr << endl
         << p << endl
         << p + 1 << endl
         << &arr[1] << endl;
}
0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884

可以看出数组的地址与数字第一个元素的地址一致,并且对指针加一运算后,其指向的元素为数组第二个元素。Go 中的数组也是如此,不过区别在于指针无法偏移,例如

go
func main() {
   arr := [5]int{0, 1, 2, 3, 4}
   p := &arr
   println(&arr[0])
   println(p)
   // 试图进行指针运算
   p++
   fmt.Println(p)
}

这样的程序将无法通过编译,报错如下

main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)

TIP

标准库unsafe提供了许多用于低级编程的操作,其中就包括指针运算,前往标准库-unsafe了解细节。

new 和 make ​

在前面的几节已经很多次提到过内置函数new和make,两者有点类似,但也有不同,下面复习下。

go
func new(Type) *Type
  • 返回值是类型指针
  • 接收参数是类型
  • 专用于给指针分配内存空间
go
func make(t Type, size ...IntegerType) Type
  • 返回值是值,不是指针
  • 接收的第一个参数是类型,不定长参数根据传入类型的不同而不同
  • 专用于给切片,映射表,通道分配内存。

下面是一些例子:

go
new(int) // int指针
new(string) // string指针
new([]int) // 整型切片指针
make([]int, 10, 100) // 长度为10,容量100的整型切片
make(map[string]int, 10) // 容量为10的映射表
make(chan int, 10) // 缓冲区大小为10的通道

完整示例 ​

下面的程序可以直接运行:复制到文件后执行 go run main.go,或粘贴到 Go Playground 运行。

go
package main

import "fmt"

type Point struct{ X, Y int }

func swap(a, b *int) {
	*a, *b = *b, *a
}

func (p *Point) Move(dx, dy int) {
	p.X += dx
	p.Y += dy
}

func main() {
	x, y := 1, 2
	swap(&x, &y)
	fmt.Println(x, y)

	p := &Point{X: 1, Y: 2}
	p.Move(3, 4)
	fmt.Println(*p)

	var np *Point
	fmt.Println(np == nil)
}

预期输出:

text
2 1
{4 6}
true

常见错误与踩坑 ​

  • Go 没有指针运算。 需要更底层操作时只能用 unsafe.Pointer,且应当尽量避免。
  • 解引用 nil 指针会 panic。 指针可能为空时,使用前先判空。
  • 取局部变量的地址是安全的。 逃逸分析会在需要时把变量移动到堆上。
  • 大结构体用指针,小值直接传值。 拷贝一个 int 比解引用更快。
  • map 的元素不可寻址。 &m["key"] 无法编译;应取出副本修改后再写回。

更新记录 ​

  • 2026-10-14:新增"阅读提示"、完整可运行示例(含预期输出)、常见错误与踩坑清单,以及本更新记录。