指针在Go语言中被拆分成为两个核心概念:
类型指针: 允许对这个指针类型的数据进行修改,传递数据使用指针,无需拷贝数据,但是类型指针不能进行偏移和运算。
切片: 由指向起始元素的原始指针元素数量(len)容量(cap)组成。

指针和变量

指针(pointer)就是一个变量,用于存储另一个变量的内存地址,所以它表示内存地址的指向。
变量是一种占位符,用于引用计算机的内存地址,可以理解为内存地址的标签。

变量和指针在内存中的表示

每一个变量在程序运行的时候都有一个内存地址,这个地址表示了变量在内存中存放的位置,Go语言中使用 & 符放在变量前面来对变量进行 “取地址” 的操作,从而可以获得这个变量的指针变量,指针变量的值就是变量的地址,代码表示如下:

1
2
3
4
var a int = 10   /*定一个整型变量*/
var ptr *int /*定义一个指针变量*/

ptr = &a /*对变量a进行取地址的操作*/

由上面的代码可以知道 a 是一个普通变量,ptr 为一个指针类型的指针变量,里面存放着变量的地址,而指针变量自身也是一个变量,在内存中也会有自己的地址,从上面的图中也可以看出。
取地址操作之后会得到变量的指针变量,需要通过指针变量来获取变量的值,就需要对指针变量进行取值操作,这个操作可以称之为 “解指针”,Go语言中使用 * 符号进行指针变量的取值,从而获取指针变量指向的原变量的值。

1
2
3
4
5
6
var a int = 10   /*定一个整型变量*/
var ptr *int /*定义一个指针变量*/

ptr = &a /*对变量a进行取地址的操作*/

b := *ptr /*b中存放的就是原变量的值*/

多重指针

指针可以指向任何类型的变量,这就意味着它可以指向另一个指针变量,下面的代码展示如何创建一个指向指针的指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main 

import "fmt"

func main() {
a := 3.14
ptr := &a
pptr := &ptr

fmt.Println("a:", a)
fmt.Println("a_addr:", &a)
fmt.Println("ptr:", ptr)
fmt.Println("ptr_addr:", &ptr)
fmt.Println("pptr:", pptr)
fmt.Println("*pptr:", *pptr)
fmt.Println("**pptr:", **pptr)
}

结果输出:

1
2
3
4
5
6
7
a: 3.14
a_addr: 0xc00000a298
ptr: 0xc00000a298
ptr_addr: 0xc000006038
pptr: 0xc000006038
*pptr: 0xc00000a298
**pptr: 3.14

数组和切片

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩的动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组。

数组是具有相同唯一类型的一组已经编号且长度固定的数据项序列,数组的最大为容量为2GB,它是值类型。切片是对数组一个连续片段的引用,所以切片是一个引用类型

按值传递和按引用传递

Go语言中的参数有两种传递方式,按值传递和按引用传递。Go默认使用按值传递来传递参数,也就是传递参数的副本。在函数中对副本的值进行修改等操作时,是不会影响到原来的变量的值。
按引用传递其实也可以认为是 “按值传递” ,只不过是该副本是一个地址的拷贝,不再是值得拷贝,通过修改这个值指向的地址上的值就可以修改原变量的值。
Go语言中,在函数调用的时候,引用类型(slice, map, interface, channel)都是默认引用传递。

数组传递时的缺点

一般情况下,传递指针的消耗比传递副本要少,尤其是数组比较大的时候,具体的原因是:

  • 值传递需要完整的复制初始数组并将这份拷贝放到栈中,这将消耗大量的时间和内存,从而效率低下。
  • 编译程序需要专门产生一部分用来复制数组的代码,这将使程序变大(这应该是个小事情)

如何避免

有两种方法可以解决这个问题:
1.使用指针,即使用引用类型
2.使用切片,因为切片就是引用类型,默认使用引用传递

使用指针进行传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func Sum(a *[5]int) int {
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}

return s
}


func main() {
arr := [5]int{1, 2, 3, 4, 5}

fmt.Println(Sum(&arr))
}

使用切片进行传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Sum(a []int) int {
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}

return s
}

func main() {
arr := [5]int{1, 2, 3, 4, 5}

fmt.Println(Sum(arr[:]))
}

提示: 最后一种方法通常比较常用。