5. 指针

指针是 Go 语言中又一重要的语言特性和数据类型,简单来说,指针即某个值的地址,在其他的高级语言,如 C/C++ 中也存在着指针的概念,但相比较 C/C++ 中的指针,Go 语言中的指针的设计是保留指针的高性能的同时,降低指针在致用过程中的复杂度。

5.1. 指针的概念

指针在 Go 语言中是一种数据类型,表示的是某个值的地址,使用 * 定义,同时使用 & 来获取变量的地址,如下代码所示:

func main() {
	a := 6
	var p *int = &a
	fmt.Println(p) // 0x27765502c008
}

上述代码先定义一个整型变量 a,再定义一个整型的指针 p,该指针指向变量 a,通过 & 获取变量 a 的地址,其示意图如下所示:

除了上述直接指向一个变量,还可以直接声明指针,如下代码:

func main() {
	var p *int

	fmt.Println(p) // <nil>
}

指针的默认值都是 nil,表示空指针。另外,也可以使用 new() 函数初始化一个指针,代码如下:

func main() {
	p := new(int)

	fmt.Println(p)
}

你会发现通过 new() 函数初始化出来的指针并不是一个空指针。

5.2. 指针的使用场景

5.2.1. 改变变量的值

此时指针 p 指向的是变量 a 存储的位置,要想修改变量 a 的值,仅需要使用 * 对指针 p 解引用即可,代码如下:

func main() {
	a := 6
	var p *int = &a
	
	// 修改变量 a 的值
	*p = 7
	fmt.Println(a) // 7
}

5.2.2. 作为函数的入参

函数的概念将在后续的章节中详细介绍到,现在只需要了解函数的概念,函数就是一段特定功能的代码,如实现加法的函数 add,代码如下:

func add(a int, b int) int {
	return a+b
}

函数 add 的输入(也称为入参)为整形的 a 和 b,输出(也称为出参)也是整型,上述便是一个基本的函数的形式,要想调用函数,则有如下的代码:

func main() {
	a := 1
	b := 2
	c := add(a, b)

	fmt.Println(c)
}

现在要想实现整型 a 和 b 两个数的交换,我们该怎么做呢?想到的是直接交换,直接上代码,如下:

func exchange(a int, b int) {
	tmp := a
	a = b
	b = tmp
}

func main() {
	a := 1
	b := 2
	
	fmt.Println(a, b)
	exchange(a, b)
	fmt.Println(a, b)
}

我们发现整型变量 a 和 b 的值并没有改变。因为入参中只是整型变量 a 和 b 的副本,在函数 exchange 中的整型变量 a 和 b 的值是改变,如下代码所示:

func exchange(a int, b int) {
	fmt.Println("in func: before -> ", a, b)
	tmp := a
	a = b
	b = tmp
	fmt.Println("in func: after -> ", a, b)
}

func main() {
	a := 1
	b := 2
	
	fmt.Println(a, b)
	exchange(a, b)
	fmt.Println(a, b)
}

程序运行后输出的结果为:

1 2
in func: before ->  1 2
in func: after ->  2 1
1 2

我们发现在函数 exchange 中,整型变量 a 和 b 的值是改变了,那有什么办法交换真实的整型变量 a 和 b 呢?可以使用指针,代码如下:

func exchange(a *int, b *int) {
	tmp := *a
	*a = *b // 改变指针 a 指向变量的值
	*b = tmp // 改变指针 b 指向变量的值
}

func main() {
	a := 1
	b := 2
	
	fmt.Println(a, b)
	exchange(&a, &b)
	fmt.Println(a, b)
}

程序运行后输出的结果为:

1 2
2 1

在调用函数 exchange() 时,输入的入参是整型变量 a 和 b 的地址,在函数 exchange() 中,改变的是指针指向的变量的值,而不再是副本,也就是指针本身。

5.3. 与 C/C++ 中指针的对比

与 C/C++ 中指针相比,Go 语言中支持的指针相关的操作包括:

  • 取地址,如通过取地址运算符 & 取到变量的地址
  • 解引用,如通过运算符 * 取到指针指向的变量的值

除了上述基本的操作外,Go 语言中不支持的操作包括:

  • 指针的运算
  • 无需手动释放内存
  • 在 Go 语言中,空指针为 nil

5.4. 本章小结

指针为 Go 语言的操作带来了便利,方便直接对内存中变量的修改,同时在函数传参的过程中,通过传入指针,可以方便的修改原始的参数。同时,在 Go 语言中也删除了指针的复杂操作,如指针的运算,无需手动释放内存等。